Support for lab auto rageshake UISI

This commit is contained in:
Valere 2021-10-22 18:10:27 +02:00
parent 6a34b999f2
commit be119ea161
11 changed files with 430 additions and 17 deletions

View file

@ -36,6 +36,7 @@
<w>ssss</w> <w>ssss</w>
<w>sygnal</w> <w>sygnal</w>
<w>threepid</w> <w>threepid</w>
<w>uisi</w>
<w>unpublish</w> <w>unpublish</w>
<w>unwedging</w> <w>unwedging</w>
<w>vctr</w> <w>vctr</w>

View file

@ -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<String>()
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val uisiDetectors = mutableMapOf<String, UISIDetector>()
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<String, String>(
"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)
}
}

View file

@ -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<EncryptedEventContent>()
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<Pair<E2EMessageDetected, TimerTask>>()
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
}
}
}

View file

@ -96,6 +96,7 @@ class VectorApplication :
@Inject lateinit var pinLocker: PinLocker @Inject lateinit var pinLocker: PinLocker
@Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var invitesAcceptor: InvitesAcceptor @Inject lateinit var invitesAcceptor: InvitesAcceptor
@Inject lateinit var autoRageShaker: AutoRageShaker
@Inject lateinit var vectorFileLogger: VectorFileLogger @Inject lateinit var vectorFileLogger: VectorFileLogger
@Inject lateinit var vectorAnalytics: VectorAnalytics @Inject lateinit var vectorAnalytics: VectorAnalytics
@ -117,6 +118,7 @@ class VectorApplication :
appContext = this appContext = this
vectorAnalytics.init() vectorAnalytics.init()
invitesAcceptor.initialize() invitesAcceptor.initialize()
autoRageShaker.initialize()
vectorUncaughtExceptionHandler.activate(this) vectorUncaughtExceptionHandler.activate(this)
// Remove Log handler statically added by Jitsi // Remove Log handler statically added by Jitsi

View file

@ -156,6 +156,7 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
views.bugReportEditText.text.toString(), views.bugReportEditText.text.toString(),
state.serverVersion, state.serverVersion,
views.bugReportButtonContactMe.isChecked, views.bugReportButtonContactMe.isChecked,
null,
object : BugReporter.IMXBugReportListener { object : BugReporter.IMXBugReportListener {
override fun onUploadFailed(reason: String?) { override fun onUploadFailed(reason: String?) {
try { try {
@ -198,7 +199,7 @@ class BugReportActivity : VectorBaseActivity<ActivityBugReportBinding>() {
views.bugReportProgressTextView.text = getString(R.string.send_bug_report_progress, myProgress.toString()) views.bugReportProgressTextView.text = getString(R.string.send_bug_report_progress, myProgress.toString())
} }
override fun onUploadSucceed() { override fun onUploadSucceed(reportUrl: String?) {
try { try {
when (reportType) { when (reportType) {
ReportType.BUG_REPORT -> { ReportType.BUG_REPORT -> {

View file

@ -24,6 +24,7 @@ import android.os.Build
import android.view.View import android.view.View
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.squareup.moshi.Types
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
@ -49,7 +50,9 @@ import okhttp3.Response
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import org.matrix.android.sdk.api.Matrix 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.api.util.MimeTypes
import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -93,6 +96,9 @@ class BugReporter @Inject constructor(
// boolean to cancel the bug report // boolean to cancel the bug report
private val mIsCancelled = false private val mIsCancelled = false
val adapter = MoshiProvider.providesMoshi()
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
/** /**
* Get current Screenshot * Get current Screenshot
* *
@ -141,7 +147,7 @@ class BugReporter @Inject constructor(
/** /**
* The bug report upload succeeded. * The bug report upload succeeded.
*/ */
fun onUploadSucceed() fun onUploadSucceed(reportUrl: String?)
} }
/** /**
@ -166,12 +172,14 @@ class BugReporter @Inject constructor(
theBugDescription: String, theBugDescription: String,
serverVersion: String, serverVersion: String,
canContact: Boolean = false, canContact: Boolean = false,
customFields: Map<String, String>? = null,
listener: IMXBugReportListener?) { listener: IMXBugReportListener?) {
// enumerate files to delete // enumerate files to delete
val mBugReportFiles: MutableList<File> = ArrayList() val mBugReportFiles: MutableList<File> = ArrayList()
coroutineScope.launch { coroutineScope.launch {
var serverError: String? = null var serverError: String? = null
var reportURL: String? = null
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
var bugDescription = theBugDescription var bugDescription = theBugDescription
val crashCallStack = getCrashDescription(context) val crashCallStack = getCrashDescription(context)
@ -247,9 +255,11 @@ class BugReporter @Inject constructor(
if (!mIsCancelled) { if (!mIsCancelled) {
val text = when (reportType) { val text = when (reportType) {
ReportType.BUG_REPORT -> "[Element] $bugDescription" ReportType.BUG_REPORT -> "[Element] $bugDescription"
ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription"
ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription"
ReportType.AUTO_UISI_SENDER,
ReportType.AUTO_UISI -> "[AutoUISI] $bugDescription"
} }
// build the multi part request // build the multi part request
@ -273,7 +283,11 @@ class BugReporter @Inject constructor(
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString()) .addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) .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) val buildNumber = context.getString(R.string.build_number)
if (buildNumber.isNotEmpty() && buildNumber != "0") { if (buildNumber.isNotEmpty() && buildNumber != "0") {
@ -321,11 +335,19 @@ class BugReporter @Inject constructor(
builder.addFormDataPart("label", "[Element]") builder.addFormDataPart("label", "[Element]")
when (reportType) { when (reportType) {
ReportType.BUG_REPORT -> { ReportType.BUG_REPORT -> {
/* nop */ /* nop */
} }
ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]") ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback") 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()) { if (getCrashFile(context).exists()) {
@ -417,6 +439,10 @@ class BugReporter @Inject constructor(
Timber.e(e, "## sendBugReport() : failed to parse error") 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) { if (mIsCancelled) {
listener.onUploadCancelled() listener.onUploadCancelled()
} else if (null == serverError) { } else if (null == serverError) {
listener.onUploadSucceed() listener.onUploadSucceed(reportURL)
} else { } else {
listener.onUploadFailed(serverError) listener.onUploadFailed(serverError)
} }

View file

@ -19,5 +19,7 @@ package im.vector.app.features.rageshake
enum class ReportType { enum class ReportType {
BUG_REPORT, BUG_REPORT,
SUGGESTION, SUGGESTION,
SPACE_BETA_FEEDBACK SPACE_BETA_FEEDBACK,
AUTO_UISI,
AUTO_UISI_SENDER,
} }

View file

@ -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_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_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_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" 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" 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_DEVELOPER_MODE_PREFERENCE_KEY,
SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY,
SETTINGS_LABS_ALLOW_EXTENDED_LOGS, 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_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY,
SETTINGS_USE_RAGE_SHAKE_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) 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 { fun prefSpacesShowAllRoomInHome(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME, return defaultPrefs.getBoolean(SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME,
// migration of old property // migration of old property

View file

@ -16,13 +16,26 @@
package im.vector.app.features.settings package im.vector.app.features.settings
// import im.vector.app.AutoRageShaker
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.preference.VectorSwitchPreference
import javax.inject.Inject 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 var titleRes = R.string.room_settings_labs_pref_title
override val preferenceXmlRes = R.xml.vector_settings_labs override val preferenceXmlRes = R.xml.vector_settings_labs
override fun bindPref() {} override fun bindPref() {
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_LABS_AUTO_REPORT_UISI)?.let { pref ->
pref.isChecked = vectorPreferences.labsAutoReportUISI()
pref.setOnPreferenceChangeListener { _, isChecked ->
// autoRageShaker.enable(isChecked as Boolean)
true
}
}
}
} }

View file

@ -3565,6 +3565,11 @@
<string name="labs_use_restricted_join_rule">Experimental Space - Restricted Room.</string> <string name="labs_use_restricted_join_rule">Experimental Space - Restricted Room.</string>
<string name="labs_use_restricted_join_rule_desc">Warning requires server support and experimental room version</string> <string name="labs_use_restricted_join_rule_desc">Warning requires server support and experimental room version</string>
<string name="labs_auto_report_uisi">Auto Report Decryption Errors.</string>
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
<string name="user_invites_you">%s invites you</string> <string name="user_invites_you">%s invites you</string>
<string name="looking_for_someone_not_in_space">Looking for someone not in %s?</string> <string name="looking_for_someone_not_in_space">Looking for someone not in %s?</string>

View file

@ -44,11 +44,6 @@
android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
android:title="@string/labs_show_unread_notifications_as_tab" /> android:title="@string/labs_show_unread_notifications_as_tab" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE"
android:summary="@string/labs_use_restricted_join_rule_desc"
android:title="@string/labs_use_restricted_join_rule" />
<im.vector.app.core.preference.VectorSwitchPreference <im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false" android:defaultValue="false"
@ -63,4 +58,10 @@
android:title="@string/labs_enable_polls" /> android:title="@string/labs_enable_polls" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_LABS_AUTO_REPORT_UISI"
android:title="@string/labs_auto_report_uisi"
android:summary="@string/labs_auto_report_uisi_desc"/>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>