diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index a2e408b50d..f99842f067 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -36,6 +36,7 @@
ssss
sygnal
threepid
+ uisi
unpublish
unwedging
vctr
diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt
new file mode 100644
index 0000000000..d11a24e38c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import android.content.Context
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.features.rageshake.BugReporter
+import im.vector.app.features.rageshake.ReportType
+import io.reactivex.disposables.Disposable
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.toContent
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+const val AUTO_RS_REQUEST = "im.vector.auto_rs_request"
+
+@Singleton
+class AutoRageShaker @Inject constructor(
+ private val sessionDataSource: ActiveSessionDataSource,
+ private val activeSessionHolder: ActiveSessionHolder,
+ private val bugReporter: BugReporter,
+ private val context: Context
+) : Session.Listener {
+
+ private lateinit var activeSessionDisposable: Disposable
+ private val activeSessionIds = mutableSetOf()
+ private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+ private val uisiDetectors = mutableMapOf()
+ private var currentActiveSessionId: String? = null
+
+ fun initialize() {
+ observeActiveSession()
+ }
+
+ var _enabled = false
+ fun enable(enabled: Boolean) {
+ uisiDetectors.forEach { it.value.enabled = enabled }
+ }
+
+ private fun observeActiveSession() {
+ activeSessionDisposable = sessionDataSource.observe()
+ .distinctUntilChanged()
+ .subscribe {
+ it.orNull()?.let { session ->
+ onSessionActive(session)
+ }
+ }
+ }
+
+ fun decryptionErrorDetected(target: E2EMessageDetected) {
+ if (target.source == UISIEventSource.INITIAL_SYNC) return
+ if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
+ coroutineScope.launch {
+ bugReporter.sendBugReport(
+ context = context,
+ reportType = ReportType.AUTO_UISI,
+ withDevicesLogs = true,
+ withCrashLogs = true,
+ withKeyRequestHistory = true,
+ withScreenshot = false,
+ theBugDescription = "UISI detected",
+ serverVersion = "",
+ canContact = false,
+ customFields = mapOf("auto-uisi" to buildString {
+ append("\neventId: ${target.eventId}")
+ append("\nroomId: ${target.roomId}")
+ append("\nsenderKey: ${target.senderKey}")
+ append("\nsource: ${target.source}")
+ append("\ndeviceId: ${target.senderDeviceId}")
+ append("\nuserId: ${target.senderUserId}")
+ append("\nsessionId: ${target.sessionId}")
+ }),
+ listener = object : BugReporter.IMXBugReportListener {
+ override fun onUploadCancelled() {
+ }
+
+ override fun onUploadFailed(reason: String?) {
+ }
+
+ override fun onProgress(progress: Int) {
+ }
+
+ override fun onUploadSucceed(reportUrl: String?) {
+ Timber.w("## VALR Report URL is $reportUrl")
+ // we need to send the toDevice message to the sender
+
+ coroutineScope.launch {
+ try {
+ activeSessionHolder.getSafeActiveSession()?.sendToDevice(
+ eventType = AUTO_RS_REQUEST,
+ userId = target.senderUserId,
+ deviceId = target.senderDeviceId,
+ content = mapOf(
+ "event_id" to target.eventId,
+ "room_id" to target.roomId,
+ "session_id" to target.sessionId,
+ "device_id" to target.senderDeviceId,
+ "user_id" to target.senderUserId,
+ "sender_key" to target.senderKey,
+ "matching_issue" to reportUrl
+ ).toContent()
+ )
+ } catch (failure: Throwable) {
+ Timber.w("## VALR : failed to send auto-uisi to device")
+ }
+ }
+ }
+ })
+ }
+ }
+
+ fun remoteAutoUISIRequest(event: Event) {
+ if (event.type != AUTO_RS_REQUEST) return
+ if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
+
+ coroutineScope.launch {
+ val eventId = event.content?.get("event_id")
+ val roomId = event.content?.get("room_id")
+ val sessionId = event.content?.get("session_id")
+ val deviceId = event.content?.get("device_id")
+ val userId = event.content?.get("user_id")
+ val senderKey = event.content?.get("sender_key")
+ val matchingIssue = event.content?.get("matching_issue")?.toString() ?: ""
+
+ bugReporter.sendBugReport(
+ context = context,
+ reportType = ReportType.AUTO_UISI_SENDER,
+ withDevicesLogs = true,
+ withCrashLogs = true,
+ withKeyRequestHistory = true,
+ withScreenshot = false,
+ theBugDescription = "UISI detected $matchingIssue",
+ serverVersion = "",
+ canContact = false,
+ customFields = mapOf(
+ "auto-uisi" to buildString {
+ append("\neventId: $eventId")
+ append("\nroomId: $roomId")
+ append("\nsenderKey: $senderKey")
+ append("\ndeviceId: $deviceId")
+ append("\nuserId: $userId")
+ append("\nsessionId: $sessionId")
+ },
+ "matching_issue" to matchingIssue
+ ),
+ listener = null
+ )
+ }
+ }
+
+ private val detector = UISIDetector().apply {
+ callback = object : UISIDetector.UISIDetectorCallback {
+ override val reciprocateToDeviceEventType: String
+ get() = AUTO_RS_REQUEST
+
+ override fun uisiDetected(source: E2EMessageDetected) {
+ decryptionErrorDetected(source)
+ }
+
+ override fun uisiReciprocateRequest(source: Event) {
+ remoteAutoUISIRequest(source)
+ }
+ }
+ }
+
+ fun onSessionActive(session: Session) {
+ val sessionId = session.sessionId
+ if (sessionId == currentActiveSessionId) {
+ return
+ }
+ this.currentActiveSessionId = sessionId
+ this.detector.enabled = _enabled
+ activeSessionIds.add(sessionId)
+ session.addListener(this)
+ session.addEventStreamListener(detector)
+ }
+
+ override fun onSessionStopped(session: Session) {
+ uisiDetectors.get(session.sessionId)?.let {
+ session.removeEventStreamListener(it)
+ }
+ activeSessionIds.remove(session.sessionId)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt
new file mode 100644
index 0000000000..e12bd7adeb
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/UISIDetector.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import org.matrix.android.sdk.api.session.LiveEventListener
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
+import timber.log.Timber
+import java.util.Timer
+import java.util.TimerTask
+import java.util.concurrent.Executors
+
+enum class UISIEventSource {
+ INITIAL_SYNC,
+ INCREMENTAL_SYNC,
+ PAGINATION
+}
+
+data class E2EMessageDetected(
+ val eventId: String,
+ val roomId: String,
+ val senderUserId: String,
+ val senderDeviceId: String,
+ val senderKey: String,
+ val sessionId: String,
+ val source: UISIEventSource) {
+
+ companion object {
+ fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected {
+ val encryptedContent = event.content.toModel()
+
+ return E2EMessageDetected(
+ eventId = event.eventId ?: "",
+ roomId = roomId,
+ senderUserId = event.senderId ?: "",
+ senderDeviceId = encryptedContent?.deviceId ?: "",
+ senderKey = encryptedContent?.senderKey ?: "",
+ sessionId = encryptedContent?.sessionId ?: "",
+ source = source
+ )
+ }
+ }
+}
+
+class UISIDetector : LiveEventListener {
+
+ interface UISIDetectorCallback {
+ val reciprocateToDeviceEventType: String
+ fun uisiDetected(source: E2EMessageDetected)
+ fun uisiReciprocateRequest(source: Event)
+ }
+
+ var callback: UISIDetectorCallback? = null
+
+ private val trackedEvents = mutableListOf>()
+ private val executor = Executors.newSingleThreadExecutor()
+ private val timer = Timer()
+ private val timeoutMillis = 30_000L
+ var enabled = false
+
+ override fun onLiveEvent(roomId: String, event: Event) {
+ if (!event.isEncrypted()) return
+ executor.execute {
+ handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC))
+ }
+ }
+
+ override fun onPaginatedEvent(roomId: String, event: Event) {
+ if (!event.isEncrypted()) return
+ executor.execute {
+ handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION))
+ }
+ }
+
+ override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) {
+ executor.execute {
+ unTrack(eventId, roomId)
+ }
+ }
+
+ override fun onLiveToDeviceEvent(event: Event) {
+ if (event.type == callback?.reciprocateToDeviceEventType) {
+ callback?.uisiReciprocateRequest(event)
+ }
+ }
+
+ override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) {
+ executor.execute {
+ unTrack(eventId, roomId)?.let {
+ triggerUISI(it)
+ }
+// if (throwable is MXCryptoError.OlmError) {
+// if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
+// unTrack(eventId, roomId)?.let {
+// triggerUISI(it)
+// }
+// }
+// }
+ }
+ }
+
+ private fun handleEventReceived(detectorEvent: E2EMessageDetected) {
+ if (trackedEvents.any { it.first == detectorEvent }) {
+ Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked")
+ } else {
+ // track it and start timer
+ val timeoutTask = object : TimerTask() {
+ override fun run() {
+ executor.execute {
+ unTrack(detectorEvent.eventId, detectorEvent.roomId)
+ Timber.v("## UISIDetector: Timeout on ${detectorEvent.eventId} ")
+ triggerUISI(detectorEvent)
+ }
+ }
+ }
+ trackedEvents.add(detectorEvent to timeoutTask)
+ timer.schedule(timeoutTask, timeoutMillis)
+ }
+ }
+
+ private fun triggerUISI(source: E2EMessageDetected) {
+ Timber.i("## UISIDetector: Unable To Decrypt $source")
+ callback?.uisiDetected(source)
+ }
+
+ private fun unTrack(eventId: String, roomId: String): E2EMessageDetected? {
+ val index = trackedEvents.indexOfFirst { it.first.eventId == eventId && it.first.roomId == roomId }
+ return if (index != -1) {
+ trackedEvents.removeAt(index).let {
+ it.second.cancel()
+ it.first
+ }
+ } else {
+ null
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt
index 400fb7eb89..05d20662c7 100644
--- a/vector/src/main/java/im/vector/app/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/app/VectorApplication.kt
@@ -96,6 +96,7 @@ class VectorApplication :
@Inject lateinit var pinLocker: PinLocker
@Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var invitesAcceptor: InvitesAcceptor
+ @Inject lateinit var autoRageShaker: AutoRageShaker
@Inject lateinit var vectorFileLogger: VectorFileLogger
@Inject lateinit var vectorAnalytics: VectorAnalytics
@@ -117,6 +118,7 @@ class VectorApplication :
appContext = this
vectorAnalytics.init()
invitesAcceptor.initialize()
+ autoRageShaker.initialize()
vectorUncaughtExceptionHandler.activate(this)
// Remove Log handler statically added by Jitsi
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
index b27c2e9818..7455a0e5bd 100755
--- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
@@ -156,6 +156,7 @@ class BugReportActivity : VectorBaseActivity() {
views.bugReportEditText.text.toString(),
state.serverVersion,
views.bugReportButtonContactMe.isChecked,
+ null,
object : BugReporter.IMXBugReportListener {
override fun onUploadFailed(reason: String?) {
try {
@@ -198,7 +199,7 @@ class BugReportActivity : VectorBaseActivity() {
views.bugReportProgressTextView.text = getString(R.string.send_bug_report_progress, myProgress.toString())
}
- override fun onUploadSucceed() {
+ override fun onUploadSucceed(reportUrl: String?) {
try {
when (reportType) {
ReportType.BUG_REPORT -> {
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
index 5b3d194d33..0ec36e43cc 100755
--- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
@@ -24,6 +24,7 @@ import android.os.Build
import android.view.View
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
+import com.squareup.moshi.Types
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
@@ -49,7 +50,9 @@ import okhttp3.Response
import org.json.JSONException
import org.json.JSONObject
import org.matrix.android.sdk.api.Matrix
+import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes
+import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber
import java.io.File
import java.io.IOException
@@ -93,6 +96,9 @@ class BugReporter @Inject constructor(
// boolean to cancel the bug report
private val mIsCancelled = false
+ val adapter = MoshiProvider.providesMoshi()
+ .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
+
/**
* Get current Screenshot
*
@@ -141,7 +147,7 @@ class BugReporter @Inject constructor(
/**
* The bug report upload succeeded.
*/
- fun onUploadSucceed()
+ fun onUploadSucceed(reportUrl: String?)
}
/**
@@ -166,12 +172,14 @@ class BugReporter @Inject constructor(
theBugDescription: String,
serverVersion: String,
canContact: Boolean = false,
+ customFields: Map? = null,
listener: IMXBugReportListener?) {
// enumerate files to delete
val mBugReportFiles: MutableList = ArrayList()
coroutineScope.launch {
var serverError: String? = null
+ var reportURL: String? = null
withContext(Dispatchers.IO) {
var bugDescription = theBugDescription
val crashCallStack = getCrashDescription(context)
@@ -247,9 +255,11 @@ class BugReporter @Inject constructor(
if (!mIsCancelled) {
val text = when (reportType) {
- ReportType.BUG_REPORT -> "[Element] $bugDescription"
- ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription"
+ ReportType.BUG_REPORT -> "[Element] $bugDescription"
+ ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription"
ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription"
+ ReportType.AUTO_UISI_SENDER,
+ ReportType.AUTO_UISI -> "[AutoUISI] $bugDescription"
}
// build the multi part request
@@ -273,7 +283,11 @@ class BugReporter @Inject constructor(
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
- .addFormDataPart("server_version", serverVersion)
+ .addFormDataPart("server_version", serverVersion).apply {
+ customFields?.forEach { (name, value) ->
+ addFormDataPart(name, value)
+ }
+ }
val buildNumber = context.getString(R.string.build_number)
if (buildNumber.isNotEmpty() && buildNumber != "0") {
@@ -321,11 +335,19 @@ class BugReporter @Inject constructor(
builder.addFormDataPart("label", "[Element]")
when (reportType) {
- ReportType.BUG_REPORT -> {
+ ReportType.BUG_REPORT -> {
/* nop */
}
- ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
+ ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback")
+ ReportType.AUTO_UISI -> {
+ builder.addFormDataPart("label", "auto-uisis-receiver")
+ builder.addFormDataPart("label", "auto-uisis")
+ }
+ ReportType.AUTO_UISI_SENDER -> {
+ builder.addFormDataPart("label", "auto-uisis-sender")
+ builder.addFormDataPart("label", "auto-uisis")
+ }
}
if (getCrashFile(context).exists()) {
@@ -417,6 +439,10 @@ class BugReporter @Inject constructor(
Timber.e(e, "## sendBugReport() : failed to parse error")
}
}
+ } else {
+ reportURL = response?.body?.string()?.let { stringBody ->
+ adapter.fromJson(stringBody)?.get("report_url")?.toString()
+ }
}
}
}
@@ -434,7 +460,7 @@ class BugReporter @Inject constructor(
if (mIsCancelled) {
listener.onUploadCancelled()
} else if (null == serverError) {
- listener.onUploadSucceed()
+ listener.onUploadSucceed(reportURL)
} else {
listener.onUploadFailed(serverError)
}
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt
index 44682efb40..f9dc628914 100644
--- a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt
@@ -19,5 +19,7 @@ package im.vector.app.features.rageshake
enum class ReportType {
BUG_REPORT,
SUGGESTION,
- SPACE_BETA_FEEDBACK
+ SPACE_BETA_FEEDBACK,
+ AUTO_UISI,
+ AUTO_UISI_SENDER,
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 3f423696ae..7c2b983859 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -152,6 +152,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_LABS_ALLOW_EXTENDED_LOGS = "SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
const val SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE = "SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE"
const val SETTINGS_LABS_SPACES_HOME_AS_ORPHAN = "SETTINGS_LABS_SPACES_HOME_AS_ORPHAN"
+ const val SETTINGS_LABS_AUTO_REPORT_UISI = "SETTINGS_LABS_AUTO_REPORT_UISI"
const val SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME = "SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME"
private const val SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
@@ -245,7 +246,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY,
SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY,
SETTINGS_LABS_ALLOW_EXTENDED_LOGS,
- SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE,
+// SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE,
SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY,
SETTINGS_USE_RAGE_SHAKE_KEY,
@@ -974,6 +975,10 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_LABS_SPACES_HOME_AS_ORPHAN, false)
}
+ fun labsAutoReportUISI(): Boolean {
+ return defaultPrefs.getBoolean(SETTINGS_LABS_AUTO_REPORT_UISI, false)
+ }
+
fun prefSpacesShowAllRoomInHome(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME,
// migration of old property
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
index f457980403..76f457b5ad 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
@@ -16,13 +16,26 @@
package im.vector.app.features.settings
+// import im.vector.app.AutoRageShaker
import im.vector.app.R
+import im.vector.app.core.preference.VectorSwitchPreference
import javax.inject.Inject
-class VectorSettingsLabsFragment @Inject constructor() : VectorSettingsBaseFragment() {
+class VectorSettingsLabsFragment @Inject constructor(
+ private val vectorPreferences: VectorPreferences,
+// private val autoRageShaker: AutoRageShaker
+) : VectorSettingsBaseFragment() {
override var titleRes = R.string.room_settings_labs_pref_title
override val preferenceXmlRes = R.xml.vector_settings_labs
- override fun bindPref() {}
+ override fun bindPref() {
+ findPreference(VectorPreferences.SETTINGS_LABS_AUTO_REPORT_UISI)?.let { pref ->
+ pref.isChecked = vectorPreferences.labsAutoReportUISI()
+ pref.setOnPreferenceChangeListener { _, isChecked ->
+// autoRageShaker.enable(isChecked as Boolean)
+ true
+ }
+ }
+ }
}
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 1816e0d1fa..d77e04a6f1 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3565,6 +3565,11 @@
Experimental Space - Restricted Room.
Warning requires server support and experimental room version
+
+
+ Auto Report Decryption Errors.
+ Your system will automatically send logs when an unable to decrypt error occurs
+
%s invites you
Looking for someone not in %s?
diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
index f394d1923e..0d240fd709 100644
--- a/vector/src/main/res/xml/vector_settings_labs.xml
+++ b/vector/src/main/res/xml/vector_settings_labs.xml
@@ -44,11 +44,6 @@
android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
android:title="@string/labs_show_unread_notifications_as_tab" />
-
+
+
\ No newline at end of file