From 2ca3387ab3648034f6e3a04f497e89eb415499bb Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 17 Mar 2022 18:51:54 +0100 Subject: [PATCH] Migrate Threads and notify user --- .../android/sdk/api/MatrixConfiguration.kt | 6 +- .../lightweight/LightweightSettingsStorage.kt | 8 ++- .../session/sync/SyncResponseHandler.kt | 2 +- .../src/main/res/values/config-settings.xml | 3 +- .../im/vector/app/core/di/SingletonModule.kt | 8 ++- .../im/vector/app/features/MainActivity.kt | 2 +- .../vector/app/features/home/HomeActivity.kt | 48 +++++++++++++++ .../features/home/HomeActivityViewEvents.kt | 3 + .../features/home/HomeActivityViewModel.kt | 46 +++++++++++++++ .../features/settings/VectorPreferences.kt | 58 ++++++++++++++++++- .../settings/VectorSettingsLabsFragment.kt | 1 + .../src/main/res/xml/vector_settings_labs.xml | 4 +- 12 files changed, 177 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index c87f21d7ac..f8472319fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -60,7 +60,11 @@ data class MatrixConfiguration( /** * RoomDisplayNameFallbackProvider to provide default room display name. */ - val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider + val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider, + /** + * Thread messages default enable/disabled value + */ + val threadMessagesEnabledDefault: Boolean = false, ) { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt index 700b94a985..65c98ab872 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.lightweight import android.content.Context import androidx.core.content.edit import androidx.preference.PreferenceManager +import org.matrix.android.sdk.api.MatrixConfiguration import javax.inject.Inject /** @@ -27,7 +28,10 @@ import javax.inject.Inject * not for large data sets */ -class LightweightSettingsStorage @Inject constructor(context: Context) { +class LightweightSettingsStorage @Inject constructor( + context: Context, + val matrixConfiguration: MatrixConfiguration +) { private val sdkDefaultPrefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) @@ -38,7 +42,7 @@ class LightweightSettingsStorage @Inject constructor(context: Context) { } fun areThreadMessagesEnabled(): Boolean { - return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, false) + return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, matrixConfiguration.threadMessagesEnabledDefault) } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 1bbf54a788..ac3ae3df91 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -75,7 +75,7 @@ internal class SyncResponseHandler @Inject constructor( suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, reporter: ProgressReporter?) { - val isInitialSync = fromToken == null + var isInitialSync = fromToken == null Timber.v("Start handling sync, is InitialSync: $isInitialSync") measureTimeMillis { diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index 0121ee9ae7..40fc68bbae 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -36,8 +36,9 @@ + false - + diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index a5575ef536..211bc3987f 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -46,6 +46,7 @@ import im.vector.app.features.navigation.Navigator import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.SharedPrefPinCodeStore import im.vector.app.features.room.VectorRoomDisplayNameFallbackProvider +import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.CoroutineScope @@ -113,10 +114,13 @@ object VectorStaticModule { } @Provides - fun providesMatrixConfiguration(vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration { + fun providesMatrixConfiguration( + vectorPreferences: VectorPreferences, + vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration { return MatrixConfiguration( applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION, - roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider + roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider, + threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled() ) } diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 33b735551c..42bd2318b3 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -241,7 +241,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity // We have a session. // Check it can be opened if (sessionHolder.getActiveSession().isOpenable) { - HomeActivity.newIntent(this) + HomeActivity.newIntent(this, existingSession = true) } else { // The token is still invalid navigator.softLogout(this) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 964fb6f365..1bd61c0f9e 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -90,6 +90,7 @@ import javax.inject.Inject data class HomeActivityArgs( val clearNotification: Boolean, val accountCreation: Boolean, + val existingSession: Boolean = false, val inviteNotificationRoomId: String? = null ) : Parcelable @@ -253,6 +254,8 @@ class HomeActivity : HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn() + HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration() + is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession) }.exhaustive } homeActivityViewModel.onEach { renderState(it) } @@ -269,6 +272,49 @@ class HomeActivity : navigator.openAnalyticsOptIn(this) } + /** + * Migrating from old threads io.element.thread to new m.thread needs an initial sync to + * sync and display existing messages appropriately + */ + private fun migrateThreadsIfNeeded(checkSession: Boolean) { + + if (checkSession) { + // We should check session to ensure we will only clear cache if needed + val args = intent.getParcelableExtra(Mavericks.KEY_ARG) + if (args?.existingSession == true) { + // existingSession --> Will be true only if we came from an existing active session + Timber.i("----> Migrating threads from an existing session..") + handleThreadsMigration() + } else { + // We came from a new session and not an existing one, + // so there is no need to migrate threads while an initial synced performed + Timber.i("----> No thread migration needed, we are ok") + vectorPreferences.threadsMigrated() + } + } else { + // Proceed with migration + handleThreadsMigration() + } + } + + /** + * Clear cache and restart to invoke an initial sync for threads migration + */ + private fun handleThreadsMigration() { + Timber.i("----> Threads Migration detected, clearing cache and sync...") + vectorPreferences.threadsMigrated() + MainActivity.restartApp(this, MainActivityArgs(clearCache = true)) + } + + private fun handleNotifyUserForThreadsMigration() { + MaterialAlertDialogBuilder(this) + .setTitle("Threads, no longer experimental") + .setMessage("All \uD83C\uDF89 \uD83C\uDF89 threads created during experimental period will\n\n now be rendered as regular replies. This will be an one-off transition, as threads are now part of the matrix specification") + .setCancelable(true) + .setPositiveButton(R.string.ok) { _, _ -> } + .show() + } + private fun handleIntent(intent: Intent?) { intent?.dataString?.let { deepLink -> val resolvedLink = when { @@ -546,11 +592,13 @@ class HomeActivity : fun newIntent(context: Context, clearNotification: Boolean = false, accountCreation: Boolean = false, + existingSession: Boolean = false, inviteNotificationRoomId: String? = null ): Intent { val args = HomeActivityArgs( clearNotification = clearNotification, accountCreation = accountCreation, + existingSession = existingSession, inviteNotificationRoomId = inviteNotificationRoomId ) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index adc44a57bd..e301967884 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -25,4 +25,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents { data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents object PromptToEnableSessionPush : HomeActivityViewEvents object ShowAnalyticsOptIn : HomeActivityViewEvents + object NotifyUserForThreadsMigration : HomeActivityViewEvents + data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents + } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 35c112b63a..bb248965e4 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -51,6 +51,7 @@ import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import kotlin.coroutines.Continuation @@ -62,6 +63,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val activeSessionHolder: ActiveSessionHolder, private val reAuthHelper: ReAuthHelper, private val analyticsStore: AnalyticsStore, + private val lightweightSettingsStorage: LightweightSettingsStorage, private val vectorPreferences: VectorPreferences ) : VectorViewModel(initialState) { @@ -84,6 +86,7 @@ class HomeActivityViewModel @AssistedInject constructor( checkSessionPushIsOn() observeCrossSigningReset() observeAnalytics() + initThreadsMigration() } private fun observeAnalytics() { @@ -130,6 +133,49 @@ class HomeActivityViewModel @AssistedInject constructor( .launchIn(viewModelScope) } + /** + * Handle threads migration. The migration includes: + * - Notify users that had io.element.thread enabled from labs + * - Re-Enable m.thread to those users (that they had enabled labs threads) + * - Handle migration when threads are enabled by default + */ + private fun initThreadsMigration() { + + // Notify users + if (vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.areThreadMessagesEnabled()) { + Timber.i("----> Notify users about threads") + // Notify the user if needed that we migrated to support m.thread + // instead of io.element.thread so old thread messages will be displayed as normal timeline messages + _viewEvents.post(HomeActivityViewEvents.NotifyUserForThreadsMigration) + vectorPreferences.userNotifiedAboutThreads() + return + } + + // Migrate users with enabled lab settings + if (vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.shouldMigrateThreads()) { + Timber.i("----> Migrate threads with enabled labs") + // If user had io.element.thread enabled then enable the new thread support, + // clear cache to sync messages appropriately + vectorPreferences.setThreadMessagesEnabled() + lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled()) + // Clear Cache + _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = false)) + return + } + // Enable all users + // When we would to enable threads for all + // if(vectorPreferences.shouldMigrateThreads) --> + // vectorPreferences.setThreadMessagesEnabled() && + // lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled()) + if(vectorPreferences.shouldMigrateThreads() && vectorPreferences.areThreadMessagesEnabled()){ + Timber.i("----> Try to migrate threads") + _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = true)) + return + } + + + } + private fun observeInitialSync() { val session = activeSessionHolder.getSafeActiveSession() ?: return 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 352c5768fb..d263dc1cbb 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 @@ -201,7 +201,13 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE" private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE" - const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES" + + // This key will be used to identify clients with the old thread support enabled io.element.thread + const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES" + + // This key will be used to identify clients with the new thread support enabled m.thread + const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL" + const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED" // Possible values for TAKE_PHOTO_VIDEO_MODE const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0 @@ -1006,7 +1012,55 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true) } + /** + * Indicates whether or not thread messages are enabled + */ fun areThreadMessagesEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, false) + return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, getDefault(R.bool.settings_labs_thread_messages_default)) + } + + /** + * Manually sets thread messages enabled, useful for migrating users from io.element.thread + */ + fun setThreadMessagesEnabled() { + defaultPrefs + .edit() + .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, true) + .apply() + } + /** + * Indicates whether or not the user will be notified about the new thread support + * We should notify the user only if he had old thread support enabled + */ + fun shouldNotifyUserAboutThreads(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false) + } + + /** + * Indicates that the user have been notified about threads migration + */ + fun userNotifiedAboutThreads() { + defaultPrefs + .edit() + .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false) + .apply() + } + + /** + * Indicates whether or not we should clear cache for threads migration. + * Default value is true, for fresh installs and updates + */ + fun shouldMigrateThreads(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, true) + } + + /** + * Indicates that there no longer threads migration needed + */ + fun threadsMigrated() { + defaultPrefs + .edit() + .putBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, false) + .apply() } } 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 118e820f84..683c1e1dd2 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 @@ -42,6 +42,7 @@ class VectorSettingsLabsFragment @Inject constructor( // clear cache findPreference(VectorPreferences.SETTINGS_LABS_ENABLE_THREAD_MESSAGES)?.let { it.onPreferenceClickListener = Preference.OnPreferenceClickListener { + vectorPreferences.threadsMigrated() // Manual actions should disable the ato enable mechanism lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled()) displayLoadingView() MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 73193edfd5..5144f6fe1f 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -52,8 +52,8 @@