From 1ff4a5f212739502b05071e10f8c77dbc8330826 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Sep 2022 15:36:21 +0200 Subject: [PATCH 01/17] Ignore `AlwaysShowAction` lint issue. Also make it an error, so that developer has to explicitly disable the warning. --- library/ui-styles/src/debug/res/menu/menu_debug.xml | 5 +++-- tools/lint/lint.xml | 3 +++ .../app/features/home/room/detail/TimelineFragment.kt | 1 + vector/src/main/res/menu/menu_home.xml | 3 ++- vector/src/main/res/menu/menu_manage_space.xml | 8 +++++--- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/library/ui-styles/src/debug/res/menu/menu_debug.xml b/library/ui-styles/src/debug/res/menu/menu_debug.xml index c58a29db8f..ac98ce8e2c 100644 --- a/library/ui-styles/src/debug/res/menu/menu_debug.xml +++ b/library/ui-styles/src/debug/res/menu/menu_debug.xml @@ -14,6 +14,7 @@ android:id="@+id/menuDebug2" android:icon="@drawable/ic_debug_icon" android:title="Send" - app:showAsAction="always" /> + app:showAsAction="always" + tools:ignore="AlwaysShowAction" /> - \ No newline at end of file + diff --git a/tools/lint/lint.xml b/tools/lint/lint.xml index 1776bd341f..84f55bb715 100644 --- a/tools/lint/lint.xml +++ b/tools/lint/lint.xml @@ -19,6 +19,9 @@ + + + diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 5eb90dde4b..726d1230db 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1124,6 +1124,7 @@ class TimelineFragment : .findViewById(R.id.action_view_icon_image) .setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary)) actionView.findViewById(R.id.cart_badge).setTextOrHide("$widgetsCount") + @Suppress("AlwaysShowAction") matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) } diff --git a/vector/src/main/res/menu/menu_home.xml b/vector/src/main/res/menu/menu_home.xml index f15c31b4e9..a78907bd93 100644 --- a/vector/src/main/res/menu/menu_home.xml +++ b/vector/src/main/res/menu/menu_home.xml @@ -34,6 +34,7 @@ android:icon="@drawable/ic_filter" android:title="@string/home_filter_placeholder_home" app:iconTint="?vctr_content_secondary" - app:showAsAction="always" /> + app:showAsAction="always" + tools:ignore="AlwaysShowAction" /> diff --git a/vector/src/main/res/menu/menu_manage_space.xml b/vector/src/main/res/menu/menu_manage_space.xml index 86defa7869..171614ef3f 100644 --- a/vector/src/main/res/menu/menu_manage_space.xml +++ b/vector/src/main/res/menu/menu_manage_space.xml @@ -1,12 +1,14 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + app:showAsAction="always" + tools:ignore="AlwaysShowAction" /> - \ No newline at end of file + From bb2eb56ee69928cd90df43121f89b3c9713bda11 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Sep 2022 15:51:27 +0200 Subject: [PATCH 02/17] Add `@ChecksSdkIntAtLeast` annotation. --- .../matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt | 3 +++ .../main/java/im/vector/app/features/home/ShortcutCreator.kt | 2 ++ .../im/vector/app/features/notifications/NotificationUtils.kt | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt index 900a2e237f..acbf9ca061 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.util +import androidx.annotation.ChecksSdkIntAtLeast + interface BuildVersionSdkIntProvider { /** * Return the current version of the Android SDK. @@ -26,6 +28,7 @@ interface BuildVersionSdkIntProvider { * Checks the if the current OS version is equal or greater than [version]. * @return A `non-null` result if true, `null` otherwise. */ + @ChecksSdkIntAtLeast(parameter = 0, lambda = 1) fun whenAtLeast(version: Int, result: () -> T): T? { return if (get() >= version) { result() diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt index 861fdc64b2..e0565debf2 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.pm.ShortcutInfo import android.graphics.Bitmap import android.os.Build +import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.WorkerThread import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat @@ -32,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject +@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O private const val adaptiveIconSizeDp = 108 private const val adaptiveIconOuterSidesDp = 18 diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 8f05819fc4..6d1c77d9e9 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -34,6 +34,7 @@ import android.text.Spannable import android.text.SpannableString import android.text.style.ForegroundColorSpan import androidx.annotation.AttrRes +import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat @@ -102,6 +103,7 @@ class NotificationUtils @Inject constructor( const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2" private const val CALL_NOTIFICATION_CHANNEL_ID = "CALL_NOTIFICATION_CHANNEL_ID_V2" + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) fun supportNotificationChannels() = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) fun openSystemSettingsForSilentCategory(fragment: Fragment) { @@ -126,7 +128,6 @@ class NotificationUtils @Inject constructor( /** * Create notification channels. */ - @TargetApi(Build.VERSION_CODES.O) fun createNotificationChannels() { if (!supportNotificationChannels()) { return From 832a472b574faf1ca683034a78e453dcca2130fb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 15:22:21 +0200 Subject: [PATCH 03/17] Add `@ChecksSdkIntAtLeast` annotation, to be able to remove `AndroidVersionTestOverrider` --- .../vector/app/AndroidVersionTestOverrider.kt | 46 ------------------- .../voice/VoiceRecorderProviderTests.kt | 17 +++---- .../features/voice/VoiceRecorderProvider.kt | 8 +++- 3 files changed, 13 insertions(+), 58 deletions(-) delete mode 100644 vector/src/androidTest/java/im/vector/app/AndroidVersionTestOverrider.kt diff --git a/vector/src/androidTest/java/im/vector/app/AndroidVersionTestOverrider.kt b/vector/src/androidTest/java/im/vector/app/AndroidVersionTestOverrider.kt deleted file mode 100644 index 97333b7c98..0000000000 --- a/vector/src/androidTest/java/im/vector/app/AndroidVersionTestOverrider.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022 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.os.Build -import java.lang.reflect.Field - -/** - * Used to override [Build.VERSION.SDK_INT]. Ideally an interface should be used instead, but that approach forces us to either add suppress lint annotations - * and potentially miss an API version issue or write a custom lint rule, which seems like an overkill. - */ -object AndroidVersionTestOverrider { - - private var initialValue: Int? = null - - fun override(newVersion: Int) { - if (initialValue == null) { - initialValue = Build.VERSION.SDK_INT - } - val field = Build.VERSION::class.java.getField("SDK_INT") - setStaticField(field, newVersion) - } - - fun restore() { - initialValue?.let { override(it) } - } - - private fun setStaticField(field: Field, value: Any) { - field.isAccessible = true - field.set(null, value) - } -} diff --git a/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt b/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt index 65f81b145b..0610496dfe 100644 --- a/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt @@ -18,41 +18,36 @@ package im.vector.app.features.voice import android.os.Build import androidx.test.platform.app.InstrumentationRegistry -import im.vector.app.AndroidVersionTestOverrider +import im.vector.app.TestBuildVersionSdkIntProvider import im.vector.app.features.DefaultVectorFeatures import io.mockk.every import io.mockk.spyk import org.amshove.kluent.shouldBeInstanceOf -import org.junit.After import org.junit.Test class VoiceRecorderProviderTests { private val context = InstrumentationRegistry.getInstrumentation().targetContext - private val provider = spyk(VoiceRecorderProvider(context, DefaultVectorFeatures())) - - @After - fun tearDown() { - AndroidVersionTestOverrider.restore() - } + private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider() + private val provider = spyk(VoiceRecorderProvider(context, DefaultVectorFeatures(), buildVersionSdkIntProvider)) @Test fun provideVoiceRecorderOnAndroidQAndCodecReturnsQRecorder() { - AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q) + buildVersionSdkIntProvider.value = Build.VERSION_CODES.Q every { provider.hasOpusEncoder() } returns true provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderQ::class) } @Test fun provideVoiceRecorderOnAndroidQButNoCodecReturnsLRecorder() { - AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q) + buildVersionSdkIntProvider.value = Build.VERSION_CODES.Q every { provider.hasOpusEncoder() } returns false provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderL::class) } @Test fun provideVoiceRecorderOnOlderAndroidVersionReturnsLRecorder() { - AndroidVersionTestOverrider.override(Build.VERSION_CODES.LOLLIPOP) + buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderL::class) } } diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt index 1bf289fb4c..c024e0c6d4 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt @@ -20,14 +20,17 @@ import android.content.Context import android.media.MediaCodecList import android.media.MediaFormat import android.os.Build +import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.VisibleForTesting import im.vector.app.features.VectorFeatures import kotlinx.coroutines.Dispatchers +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import javax.inject.Inject class VoiceRecorderProvider @Inject constructor( private val context: Context, private val vectorFeatures: VectorFeatures, + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, ) { fun provideVoiceRecorder(): VoiceRecorder { return if (useFallbackRecorder()) { @@ -37,8 +40,11 @@ class VoiceRecorderProvider @Inject constructor( } } + @ChecksSdkIntAtLeast(api = 29) private fun useFallbackRecorder(): Boolean { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || !hasOpusEncoder() || vectorFeatures.forceUsageOfOpusEncoder() + return buildVersionSdkIntProvider.get() < Build.VERSION_CODES.Q || + !hasOpusEncoder() || + vectorFeatures.forceUsageOfOpusEncoder() } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) From 7e8a39e6de80ff749e55b5b78cf2c6dc59b80b8e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Sep 2022 16:02:28 +0200 Subject: [PATCH 04/17] Suppress `LaunchActivityFromNotification`. This is fine here, this is the notification for the diagnostic, we do not want to start an Activity. --- tools/lint/lint.xml | 3 +++ .../im/vector/app/features/notifications/NotificationUtils.kt | 1 + 2 files changed, 4 insertions(+) diff --git a/tools/lint/lint.xml b/tools/lint/lint.xml index 84f55bb715..8b4b6ef617 100644 --- a/tools/lint/lint.xml +++ b/tools/lint/lint.xml @@ -108,6 +108,9 @@ + + + diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 6d1c77d9e9..d00c1dcc42 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -967,6 +967,7 @@ class NotificationUtils @Inject constructor( } } + @SuppressLint("LaunchActivityFromNotification") fun displayDiagnosticNotification() { val testActionIntent = Intent(context, TestNotificationReceiver::class.java) testActionIntent.action = actionIds.diagnostic From d8436874e2c2d3338c128c9234701639965ae23d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 11:41:41 +0200 Subject: [PATCH 05/17] Fix `StaticFieldLeak` issue (context). Make VectorLocal an injectable class. --- tools/lint/lint.xml | 1 + .../java/im/vector/app/VectorApplication.kt | 3 +- .../app/core/platform/VectorBaseActivity.kt | 4 ++- .../features/call/conference/JitsiService.kt | 3 +- .../call/dialpad/CallDialPadBottomSheet.kt | 7 ++++- .../features/call/dialpad/PstnDialActivity.kt | 2 +- .../call/transfer/CallTransferActivity.kt | 2 +- .../call/transfer/CallTransferPagerAdapter.kt | 5 ++-- .../configuration/VectorConfiguration.kt | 15 +++++----- .../setup/KeysBackupSetupStep2Fragment.kt | 7 +++-- .../BootstrapEnterPassphraseFragment.kt | 7 +++-- .../app/features/home/HomeDetailFragment.kt | 3 +- .../app/features/rageshake/BugReporter.kt | 3 +- .../app/features/settings/VectorLocale.kt | 29 ++++++++++--------- .../VectorSettingsPreferencesFragment.kt | 3 +- .../settings/locale/LocalePickerController.kt | 16 +++++----- .../settings/locale/LocalePickerViewModel.kt | 12 ++++++-- .../settings/locale/LocalePickerViewState.kt | 3 +- 18 files changed, 78 insertions(+), 47 deletions(-) diff --git a/tools/lint/lint.xml b/tools/lint/lint.xml index 8b4b6ef617..3d3b073749 100644 --- a/tools/lint/lint.xml +++ b/tools/lint/lint.xml @@ -80,6 +80,7 @@ + diff --git a/vector-app/src/main/java/im/vector/app/VectorApplication.kt b/vector-app/src/main/java/im/vector/app/VectorApplication.kt index ee04d908e8..5e789d9504 100644 --- a/vector-app/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector-app/src/main/java/im/vector/app/VectorApplication.kt @@ -109,6 +109,7 @@ class VectorApplication : @Inject lateinit var fcmHelper: FcmHelper @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var leakDetector: LeakDetector + @Inject lateinit var vectorLocale: VectorLocale // font thread handler private var fontThreadHandler: Handler? = null @@ -159,7 +160,7 @@ class VectorApplication : R.array.com_google_android_gms_fonts_certs ) FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) - VectorLocale.init(this, buildMeta) + vectorLocale.init() ThemeUtils.init(this) vectorConfiguration.applyToApplicationContext() diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index b4ba384f8f..fbcfd69610 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -84,6 +84,7 @@ import im.vector.app.features.rageshake.RageShake import im.vector.app.features.session.SessionListener import im.vector.app.features.settings.FontScalePreferences import im.vector.app.features.settings.FontScalePreferencesImpl +import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils @@ -155,6 +156,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver @Inject lateinit var rageShake: RageShake @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var fontScalePreferences: FontScalePreferences + @Inject lateinit var vectorLocale: VectorLocale // For debug only @Inject lateinit var debugReceiver: DebugReceiver @@ -177,7 +179,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver override fun attachBaseContext(base: Context) { val fontScalePreferences = FontScalePreferencesImpl(PreferenceManager.getDefaultSharedPreferences(base), AndroidSystemSettingsProvider(base)) - val vectorConfiguration = VectorConfiguration(this, fontScalePreferences) + val vectorConfiguration = VectorConfiguration(this, fontScalePreferences, vectorLocale) super.attachBaseContext(vectorConfiguration.getLocalisedContext(base)) } diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt index b0da4f0e18..bdb6cfb0d0 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt @@ -49,6 +49,7 @@ class JitsiService @Inject constructor( private val themeProvider: ThemeProvider, private val jitsiJWTFactory: JitsiJWTFactory, private val clock: Clock, + private val vectorLocale: VectorLocale, ) { companion object { @@ -163,7 +164,7 @@ class JitsiService @Inject constructor( if (widgetSessionId.length > 8) { widgetSessionId = widgetSessionId.substring(0, 7) } - roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.lowercase(VectorLocale.applicationLocale) + roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.lowercase(vectorLocale.applicationLocale) } } diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt index 8bf2ce47bd..be38f7d509 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt @@ -20,12 +20,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.addChildFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetCallDialPadBinding import im.vector.app.features.settings.VectorLocale +import javax.inject.Inject +@AndroidEntryPoint class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment() { companion object { @@ -41,6 +44,8 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment() { } } - sectionsPagerAdapter = CallTransferPagerAdapter(this) + sectionsPagerAdapter = CallTransferPagerAdapter(this, vectorLocale) views.callTransferViewPager.adapter = sectionsPagerAdapter TabLayoutMediator(views.callTransferTabLayout, views.callTransferViewPager) { tab, position -> diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferPagerAdapter.kt index 3ec8f61978..f5ab172585 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferPagerAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferPagerAdapter.kt @@ -27,7 +27,8 @@ import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs class CallTransferPagerAdapter( - private val fragmentActivity: FragmentActivity + private val fragmentActivity: FragmentActivity, + private val vectorLocale: VectorLocale, ) : FragmentStateAdapter(fragmentActivity) { companion object { @@ -61,7 +62,7 @@ class CallTransferPagerAdapter( arguments = Bundle().apply { putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true) putBoolean(DialPadFragment.EXTRA_ENABLE_OK, false) - putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) + putString(DialPadFragment.EXTRA_REGION_CODE, vectorLocale.applicationLocale.country) } } } diff --git a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt index a3d801e534..f67706dbd7 100644 --- a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt +++ b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt @@ -33,21 +33,22 @@ import javax.inject.Inject */ class VectorConfiguration @Inject constructor( private val context: Context, - private val fontScalePreferences: FontScalePreferences + private val fontScalePreferences: FontScalePreferences, + private val vectorLocale: VectorLocale, ) { fun onConfigurationChanged() { - if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) { + if (Locale.getDefault().toString() != vectorLocale.applicationLocale.toString()) { Timber.v("## onConfigurationChanged(): the locale has been updated to ${Locale.getDefault()}") - Timber.v("## onConfigurationChanged(): restore the expected value ${VectorLocale.applicationLocale}") - Locale.setDefault(VectorLocale.applicationLocale) + Timber.v("## onConfigurationChanged(): restore the expected value ${vectorLocale.applicationLocale}") + Locale.setDefault(vectorLocale.applicationLocale) } // Night mode may have changed ThemeUtils.init(context) } fun applyToApplicationContext() { - val locale = VectorLocale.applicationLocale + val locale = vectorLocale.applicationLocale val fontScale = fontScalePreferences.getResolvedFontScaleValue() Locale.setDefault(locale) @@ -67,7 +68,7 @@ class VectorConfiguration @Inject constructor( */ fun getLocalisedContext(context: Context): Context { try { - val locale = VectorLocale.applicationLocale + val locale = vectorLocale.applicationLocale // create new configuration passing old configuration from original Context val configuration = Configuration(context.resources.configuration) @@ -107,7 +108,7 @@ class VectorConfiguration @Inject constructor( * @return the local status value */ fun getHash(): String { - return (VectorLocale.applicationLocale.toString() + + return (vectorLocale.applicationLocale.toString() + "_" + fontScalePreferences.getResolvedFontScaleValue().preferenceValue + "_" + ThemeUtils.getApplicationTheme(context)) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt index cf92afcc2e..4ee6126fb7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt @@ -32,6 +32,7 @@ import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding import im.vector.app.features.settings.VectorLocale import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import javax.inject.Inject @AndroidEntryPoint class KeysBackupSetupStep2Fragment : @@ -43,6 +44,8 @@ class KeysBackupSetupStep2Fragment : private val zxcvbn = Zxcvbn() + @Inject lateinit var vectorLocale: VectorLocale + private fun onPassphraseChanged() { viewModel.passphrase.value = views.keysBackupSetupStep2PassphraseEnterEdittext.text.toString() viewModel.confirmPassphraseError.value = null @@ -78,12 +81,12 @@ class KeysBackupSetupStep2Fragment : views.keysBackupSetupStep2PassphraseStrengthLevel.strength = score if (score in 1..3) { - val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale) + val warning = strength.feedback?.getWarning(vectorLocale.applicationLocale) if (warning != null) { views.keysBackupSetupStep2PassphraseEnterTil.error = warning } - val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale) + val suggestions = strength.feedback?.getSuggestions(vectorLocale.applicationLocale) if (suggestions != null) { views.keysBackupSetupStep2PassphraseEnterTil.error = suggestions.firstOrNull() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt index 43cc25f195..de04e59245 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges +import javax.inject.Inject @AndroidEntryPoint class BootstrapEnterPassphraseFragment : @@ -43,6 +44,8 @@ class BootstrapEnterPassphraseFragment : return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false) } + @Inject lateinit var vectorLocale: VectorLocale + val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -105,8 +108,8 @@ class BootstrapEnterPassphraseFragment : views.ssssPassphraseSecurityProgress.strength = score if (score in 1..3) { val hint = - strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() } - ?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull() + strength.feedback?.getWarning(vectorLocale.applicationLocale)?.takeIf { it.isNotBlank() } + ?: strength.feedback?.getSuggestions(vectorLocale.applicationLocale)?.firstOrNull() if (hint != null && hint != views.ssssPassphraseEnterTil.error.toString()) { views.ssssPassphraseEnterTil.error = hint } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 8eab759fcd..e4fa267af6 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -75,6 +75,7 @@ class HomeDetailFragment : @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var spaceStateHandler: SpaceStateHandler + @Inject lateinit var vectorLocale: VectorLocale private val viewModel: HomeDetailViewModel by fragmentViewModel() private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel() @@ -378,7 +379,7 @@ class HomeDetailFragment : arguments = Bundle().apply { putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true) putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true) - putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) + putString(DialPadFragment.EXTRA_REGION_CODE, vectorLocale.applicationLocale.country) } applyCallback() } 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 eefbf63a12..d22f3f9b58 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 @@ -80,6 +80,7 @@ class BugReporter @Inject constructor( private val buildMeta: BuildMeta, private val processInfo: ProcessInfo, private val sdkIntProvider: BuildVersionSdkIntProvider, + private val vectorLocale: VectorLocale, ) { var inMultiWindowMode = false @@ -294,7 +295,7 @@ class BugReporter @Inject constructor( Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME ) .addFormDataPart("locale", Locale.getDefault().toString()) - .addFormDataPart("app_language", VectorLocale.applicationLocale.toString()) + .addFormDataPart("app_language", vectorLocale.applicationLocale.toString()) .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) .addFormDataPart("server_version", serverVersion) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt index 4666d586d3..438434ed3c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt @@ -27,19 +27,27 @@ import kotlinx.coroutines.withContext import timber.log.Timber import java.util.IllformedLocaleException import java.util.Locale +import javax.inject.Inject +import javax.inject.Singleton /** * Object to manage the Locale choice of the user. */ -object VectorLocale { - private const val APPLICATION_LOCALE_COUNTRY_KEY = "APPLICATION_LOCALE_COUNTRY_KEY" - private const val APPLICATION_LOCALE_VARIANT_KEY = "APPLICATION_LOCALE_VARIANT_KEY" - private const val APPLICATION_LOCALE_LANGUAGE_KEY = "APPLICATION_LOCALE_LANGUAGE_KEY" - private const val APPLICATION_LOCALE_SCRIPT_KEY = "APPLICATION_LOCALE_SCRIPT_KEY" +@Singleton +class VectorLocale @Inject constructor( + private val context: Context, + private val buildMeta: BuildMeta, +) { + companion object { + private const val APPLICATION_LOCALE_COUNTRY_KEY = "APPLICATION_LOCALE_COUNTRY_KEY" + private const val APPLICATION_LOCALE_VARIANT_KEY = "APPLICATION_LOCALE_VARIANT_KEY" + private const val APPLICATION_LOCALE_LANGUAGE_KEY = "APPLICATION_LOCALE_LANGUAGE_KEY" + private const val APPLICATION_LOCALE_SCRIPT_KEY = "APPLICATION_LOCALE_SCRIPT_KEY" + private const val ISO_15924_LATN = "Latn" + } private val defaultLocale = Locale("en", "US") - private const val ISO_15924_LATN = "Latn" /** * The cache of supported application languages. @@ -52,15 +60,10 @@ object VectorLocale { var applicationLocale = defaultLocale private set - private lateinit var context: Context - private lateinit var buildMeta: BuildMeta - /** - * Init this object. + * Init this singleton. */ - fun init(context: Context, buildMeta: BuildMeta) { - this.context = context - this.buildMeta = buildMeta + fun init() { val preferences = DefaultSharedPreferences.getInstance(context) if (preferences.contains(APPLICATION_LOCALE_LANGUAGE_KEY)) { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 3c8ec56713..073d5f7468 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -46,6 +46,7 @@ class VectorSettingsPreferencesFragment : @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var fontScalePreferences: FontScalePreferences @Inject lateinit var vectorFeatures: VectorFeatures + @Inject lateinit var vectorLocale: VectorLocale override var titleRes = R.string.settings_preferences override val preferenceXmlRes = R.xml.vector_settings_preferences @@ -198,7 +199,7 @@ class VectorSettingsPreferencesFragment : private fun setUserInterfacePreferences() { // Selected language - selectedLanguagePreference.summary = VectorLocale.localeToLocalisedString(VectorLocale.applicationLocale) + selectedLanguagePreference.summary = vectorLocale.localeToLocalisedString(vectorLocale.applicationLocale) // Text size textSizePreference.summary = getString(fontScalePreferences.getResolvedFontScaleValue().nameResId) diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt index 9853b28aae..0cbfef7495 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt @@ -37,13 +37,15 @@ import javax.inject.Inject class LocalePickerController @Inject constructor( private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider, - private val errorFormatter: ErrorFormatter + private val errorFormatter: ErrorFormatter, + private val vectorLocale: VectorLocale, ) : TypedEpoxyController() { var listener: Listener? = null override fun buildModels(data: LocalePickerViewState?) { val list = data?.locales ?: return + val currentLocale = data.currentLocale ?: return val host = this profileSectionItem { @@ -51,10 +53,10 @@ class LocalePickerController @Inject constructor( title(host.stringProvider.getString(R.string.choose_locale_current_locale_title)) } localeItem { - id(data.currentLocale.toString()) - title(VectorLocale.localeToLocalisedString(data.currentLocale).safeCapitalize(data.currentLocale)) + id(currentLocale.toString()) + title(host.vectorLocale.localeToLocalisedString(currentLocale).safeCapitalize(currentLocale)) if (host.vectorPreferences.developerMode()) { - subtitle(VectorLocale.localeToLocalisedStringInfo(data.currentLocale)) + subtitle(host.vectorLocale.localeToLocalisedStringInfo(currentLocale)) } clickListener { host.listener?.onUseCurrentClicked() } } @@ -78,13 +80,13 @@ class LocalePickerController @Inject constructor( } } else { list() - .filter { it.toString() != data.currentLocale.toString() } + .filter { it.toString() != currentLocale.toString() } .forEach { locale -> localeItem { id(locale.toString()) - title(VectorLocale.localeToLocalisedString(locale).safeCapitalize(locale)) + title(host.vectorLocale.localeToLocalisedString(locale).safeCapitalize(locale)) if (host.vectorPreferences.developerMode()) { - subtitle(VectorLocale.localeToLocalisedStringInfo(locale)) + subtitle(host.vectorLocale.localeToLocalisedStringInfo(locale)) } clickListener { host.listener?.onLocaleClicked(locale) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt index 0bbbc323e0..c38f9b5b87 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt @@ -30,7 +30,8 @@ import kotlinx.coroutines.launch class LocalePickerViewModel @AssistedInject constructor( @Assisted initialState: LocalePickerViewState, - private val vectorConfiguration: VectorConfiguration + private val vectorConfiguration: VectorConfiguration, + private val vectorLocale: VectorLocale, ) : VectorViewModel(initialState) { @AssistedFactory @@ -39,8 +40,13 @@ class LocalePickerViewModel @AssistedInject constructor( } init { + setState { + copy( + currentLocale = vectorLocale.applicationLocale + ) + } viewModelScope.launch { - val result = VectorLocale.getSupportedLocales() + val result = vectorLocale.getSupportedLocales() setState { copy( @@ -59,7 +65,7 @@ class LocalePickerViewModel @AssistedInject constructor( } private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) { - VectorLocale.saveApplicationLocale(action.locale) + vectorLocale.saveApplicationLocale(action.locale) vectorConfiguration.applyToApplicationContext() _viewEvents.post(LocalePickerViewEvents.RestartActivity) } diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt index 8cb5978393..f981e7a444 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt @@ -19,10 +19,9 @@ package im.vector.app.features.settings.locale import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized -import im.vector.app.features.settings.VectorLocale import java.util.Locale data class LocalePickerViewState( - val currentLocale: Locale = VectorLocale.applicationLocale, + val currentLocale: Locale? = null, val locales: Async> = Uninitialized ) : MavericksState From 0324927b04d384797f2e03df5acb1edbba922f8d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 15:20:23 +0200 Subject: [PATCH 06/17] Create VectorLocaleProvider, to just read the current Locale from the SharedPreference --- .../app/core/platform/VectorBaseActivity.kt | 10 +++-- .../features/call/conference/JitsiService.kt | 4 +- .../call/dialpad/CallDialPadBottomSheet.kt | 4 +- .../features/call/dialpad/PstnDialActivity.kt | 1 - .../call/transfer/CallTransferPagerAdapter.kt | 4 +- .../configuration/VectorConfiguration.kt | 4 +- .../setup/KeysBackupSetupStep2Fragment.kt | 4 +- .../BootstrapEnterPassphraseFragment.kt | 4 +- .../app/features/home/HomeDetailFragment.kt | 4 +- .../notifications/NotificationUtils.kt | 1 - .../app/features/rageshake/BugReporter.kt | 4 +- .../app/features/settings/VectorLocale.kt | 7 ++-- .../features/settings/VectorLocaleProvider.kt | 41 +++++++++++++++++++ 13 files changed, 66 insertions(+), 26 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/VectorLocaleProvider.kt diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index fbcfd69610..0b8d6698d2 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -84,7 +84,7 @@ import im.vector.app.features.rageshake.RageShake import im.vector.app.features.session.SessionListener import im.vector.app.features.settings.FontScalePreferences import im.vector.app.features.settings.FontScalePreferencesImpl -import im.vector.app.features.settings.VectorLocale +import im.vector.app.features.settings.VectorLocaleProvider import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils @@ -156,7 +156,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver @Inject lateinit var rageShake: RageShake @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var fontScalePreferences: FontScalePreferences - @Inject lateinit var vectorLocale: VectorLocale + @Inject lateinit var vectorLocale: VectorLocaleProvider // For debug only @Inject lateinit var debugReceiver: DebugReceiver @@ -178,8 +178,10 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private val restorables = ArrayList() override fun attachBaseContext(base: Context) { - val fontScalePreferences = FontScalePreferencesImpl(PreferenceManager.getDefaultSharedPreferences(base), AndroidSystemSettingsProvider(base)) - val vectorConfiguration = VectorConfiguration(this, fontScalePreferences, vectorLocale) + val preferences = PreferenceManager.getDefaultSharedPreferences(base) + val fontScalePreferences = FontScalePreferencesImpl(preferences, AndroidSystemSettingsProvider(base)) + val vectorLocaleProvider = VectorLocaleProvider(preferences) + val vectorConfiguration = VectorConfiguration(this, fontScalePreferences, vectorLocaleProvider) super.attachBaseContext(vectorConfiguration.getLocalisedContext(base)) } diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt index bdb6cfb0d0..d14f358801 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt @@ -25,7 +25,7 @@ import im.vector.app.core.utils.toBase32String import im.vector.app.features.call.conference.jwt.JitsiJWTFactory import im.vector.app.features.displayname.getBestName import im.vector.app.features.raw.wellknown.getElementWellknown -import im.vector.app.features.settings.VectorLocale +import im.vector.app.features.settings.VectorLocaleProvider import im.vector.app.features.themes.ThemeProvider import okhttp3.Request import org.jitsi.meet.sdk.JitsiMeetUserInfo @@ -49,7 +49,7 @@ class JitsiService @Inject constructor( private val themeProvider: ThemeProvider, private val jitsiJWTFactory: JitsiJWTFactory, private val clock: Clock, - private val vectorLocale: VectorLocale, + private val vectorLocale: VectorLocaleProvider, ) { companion object { diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt index be38f7d509..c157ee42b8 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/CallDialPadBottomSheet.kt @@ -25,7 +25,7 @@ import im.vector.app.R import im.vector.app.core.extensions.addChildFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetCallDialPadBinding -import im.vector.app.features.settings.VectorLocale +import im.vector.app.features.settings.VectorLocaleProvider import javax.inject.Inject @AndroidEntryPoint @@ -44,7 +44,7 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment Date: Fri, 16 Sep 2022 16:06:49 +0200 Subject: [PATCH 07/17] Remove `DefaultSharedPreferences` since we now have @DefaultPreferences which provide a singleton. Some fun has been moved to injectable classes due to this change. Not compiling, still work to do, but I prefer to split into 2 separate commits. --- .../java/im/vector/app/VectorApplication.kt | 5 +- .../app/core/di/DefaultSharedPreferences.kt | 31 ---- .../app/core/pushers/UnifiedPushStore.kt | 9 +- .../app/core/ui/views/KeysBackupBanner.kt | 1 - .../im/vector/app/core/utils/RingtoneUtils.kt | 166 +++++++++--------- .../features/disclaimer/DisclaimerDialog.kt | 50 +++--- .../vector/app/features/home/HomeActivity.kt | 5 +- .../homeserver/ServerUrlsRepository.kt | 48 ++--- .../VectorUncaughtExceptionHandler.kt | 11 +- .../app/features/settings/VectorLocale.kt | 9 +- .../features/settings/VectorPreferences.kt | 16 +- .../VectorSettingsVoiceVideoFragment.kt | 23 +-- .../vector/app/features/themes/ThemeUtils.kt | 4 +- 13 files changed, 183 insertions(+), 195 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/core/di/DefaultSharedPreferences.kt diff --git a/vector-app/src/main/java/im/vector/app/VectorApplication.kt b/vector-app/src/main/java/im/vector/app/VectorApplication.kt index 5e789d9504..ec0a6cb2a4 100644 --- a/vector-app/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector-app/src/main/java/im/vector/app/VectorApplication.kt @@ -53,7 +53,7 @@ import im.vector.app.core.resources.BuildMeta import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration -import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog +import im.vector.app.features.disclaimer.DisclaimerDialog import im.vector.app.features.invite.InvitesAcceptor import im.vector.app.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.app.features.notifications.NotificationDrawerManager @@ -110,6 +110,7 @@ class VectorApplication : @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var leakDetector: LeakDetector @Inject lateinit var vectorLocale: VectorLocale + @Inject lateinit var disclaimerDialog: DisclaimerDialog // font thread handler private var fontThreadHandler: Handler? = null @@ -172,7 +173,7 @@ class VectorApplication : val sessionImported = legacySessionImporter.process() if (!sessionImported) { // Do not display the name change popup - doNotShowDisclaimerDialog(this) + disclaimerDialog.doNotShowDisclaimerDialog() } ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver { diff --git a/vector/src/main/java/im/vector/app/core/di/DefaultSharedPreferences.kt b/vector/src/main/java/im/vector/app/core/di/DefaultSharedPreferences.kt deleted file mode 100644 index abee0cb2e7..0000000000 --- a/vector/src/main/java/im/vector/app/core/di/DefaultSharedPreferences.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2020 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.core.di - -import android.content.Context -import android.content.SharedPreferences -import androidx.preference.PreferenceManager - -object DefaultSharedPreferences { - - @Volatile private var INSTANCE: SharedPreferences? = null - - fun getInstance(context: Context): SharedPreferences = - INSTANCE ?: synchronized(this) { - INSTANCE ?: PreferenceManager.getDefaultSharedPreferences(context.applicationContext).also { INSTANCE = it } - } -} diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt index d9c6bf3159..0bdfbe8e22 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt @@ -17,16 +17,17 @@ package im.vector.app.core.pushers import android.content.Context +import android.content.SharedPreferences import androidx.core.content.edit -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import javax.inject.Inject class UnifiedPushStore @Inject constructor( val context: Context, - val fcmHelper: FcmHelper + val fcmHelper: FcmHelper, + @DefaultPreferences + private val defaultPrefs: SharedPreferences, ) { - private val defaultPrefs = DefaultSharedPreferences.getInstance(context) - /** * Retrieves the UnifiedPush Endpoint. * diff --git a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt index e789585b63..a753139c66 100755 --- a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt @@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.edit import androidx.core.view.isVisible import im.vector.app.R -import im.vector.app.core.di.DefaultSharedPreferences import im.vector.app.databinding.ViewKeysBackupBannerBinding import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt index bbed2f6000..915d840637 100644 --- a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt @@ -17,103 +17,109 @@ package im.vector.app.core.utils import android.content.Context +import android.content.SharedPreferences import android.media.Ringtone import android.media.RingtoneManager import android.net.Uri import androidx.core.content.edit -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import im.vector.app.features.settings.VectorPreferences +import javax.inject.Inject /** - * This file manages the sound ringtone for calls. - * It allows you to use the default Riot Ringtone, or the standard ringtone or set a different one from the available choices + * This class manages the sound ringtone for calls. + * It allows you to use the default Element Ringtone, or the standard ringtone or set a different one from the available choices * in Android. */ +class RingtoneUtils @Inject constructor( + @DefaultPreferences + private val sharedPreferences: SharedPreferences, + private val context: Context, +) { + /** + * Returns a Uri object that points to a specific Ringtone. + * + * If no Ringtone was explicitly set using Riot, it will return the Uri for the current system + * ringtone for calls. + * + * @return the [Uri] of the currently set [Ringtone] + * @see Ringtone + */ + fun getCallRingtoneUri(): Uri? { + val callRingtone: String? = sharedPreferences + .getString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, null) -/** - * Returns a Uri object that points to a specific Ringtone. - * - * If no Ringtone was explicitly set using Riot, it will return the Uri for the current system - * ringtone for calls. - * - * @return the [Uri] of the currently set [Ringtone] - * @see Ringtone - */ -fun getCallRingtoneUri(context: Context): Uri? { - val callRingtone: String? = DefaultSharedPreferences.getInstance(context) - .getString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, null) + callRingtone?.let { + return Uri.parse(it) + } - callRingtone?.let { - return Uri.parse(it) + return try { + // Use current system notification sound for incoming calls per default (note that it can return null) + RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE) + } catch (e: SecurityException) { + // Ignore for now + null + } } - return try { - // Use current system notification sound for incoming calls per default (note that it can return null) - RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE) - } catch (e: SecurityException) { - // Ignore for now - null - } -} + /** + * Returns a Ringtone object that can then be played. + * + * If no Ringtone was explicitly set using Riot, it will return the current system ringtone + * for calls. + * + * @return the currently set [Ringtone] + * @see Ringtone + */ + fun getCallRingtone(): Ringtone? { + getCallRingtoneUri()?.let { + // Note that it can also return null + return RingtoneManager.getRingtone(context, it) + } -/** - * Returns a Ringtone object that can then be played. - * - * If no Ringtone was explicitly set using Riot, it will return the current system ringtone - * for calls. - * - * @return the currently set [Ringtone] - * @see Ringtone - */ -fun getCallRingtone(context: Context): Ringtone? { - getCallRingtoneUri(context)?.let { - // Note that it can also return null - return RingtoneManager.getRingtone(context, it) + return null } - return null -} + /** + * Returns a String with the name of the current Ringtone. + * + * If no Ringtone was explicitly set using Riot, it will return the name of the current system + * ringtone for calls. + * + * @return the name of the currently set [Ringtone], or null + * @see Ringtone + */ + fun getCallRingtoneName(): String? { + return getCallRingtone()?.getTitle(context) + } -/** - * Returns a String with the name of the current Ringtone. - * - * If no Ringtone was explicitly set using Riot, it will return the name of the current system - * ringtone for calls. - * - * @return the name of the currently set [Ringtone], or null - * @see Ringtone - */ -fun getCallRingtoneName(context: Context): String? { - return getCallRingtone(context)?.getTitle(context) -} + /** + * Sets the selected ringtone for riot calls. + * + * @param ringtoneUri + * @see Ringtone + */ + fun setCallRingtoneUri(ringtoneUri: Uri) { + sharedPreferences + .edit { + putString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, ringtoneUri.toString()) + } + } -/** - * Sets the selected ringtone for riot calls. - * - * @param context Android context - * @param ringtoneUri - * @see Ringtone - */ -fun setCallRingtoneUri(context: Context, ringtoneUri: Uri) { - DefaultSharedPreferences.getInstance(context) - .edit { - putString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, ringtoneUri.toString()) - } -} + /** + * Set using Riot default ringtone. + */ + fun useRiotDefaultRingtone(): Boolean { + return sharedPreferences.getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true) + } -/** - * Set using Riot default ringtone. - */ -fun useRiotDefaultRingtone(context: Context): Boolean { - return DefaultSharedPreferences.getInstance(context).getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true) -} - -/** - * Ask if default Riot ringtone has to be used. - */ -fun setUseRiotDefaultRingtone(context: Context, useRiotDefault: Boolean) { - DefaultSharedPreferences.getInstance(context) - .edit { - putBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, useRiotDefault) - } + /** + * Ask if default Riot ringtone has to be used. + */ + fun setUseRiotDefaultRingtone(useRiotDefault: Boolean) { + sharedPreferences + .edit { + putBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, useRiotDefault) + } + } } diff --git a/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt b/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt index 1c6afb30b2..8214ab7120 100644 --- a/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt +++ b/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt @@ -17,44 +17,46 @@ package im.vector.app.features.disclaimer import android.app.Activity -import android.content.Context +import android.content.SharedPreferences import androidx.core.content.edit import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.features.settings.VectorSettingsUrls +import javax.inject.Inject // Increase this value to show again the disclaimer dialog after an upgrade of the application private const val CURRENT_DISCLAIMER_VALUE = 2 const val SHARED_PREF_KEY = "LAST_DISCLAIMER_VERSION_VALUE" -fun showDisclaimerDialog(activity: Activity) { - val sharedPrefs = DefaultSharedPreferences.getInstance(activity) +class DisclaimerDialog @Inject constructor( + @DefaultPreferences + private val sharedPrefs: SharedPreferences, +) { + fun showDisclaimerDialog(activity: Activity) { + if (sharedPrefs.getInt(SHARED_PREF_KEY, 0) < CURRENT_DISCLAIMER_VALUE) { + sharedPrefs.edit { + putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE) + } - if (sharedPrefs.getInt(SHARED_PREF_KEY, 0) < CURRENT_DISCLAIMER_VALUE) { + val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_disclaimer_content, null) + + MaterialAlertDialogBuilder(activity) + .setView(dialogLayout) + .setCancelable(false) + .setNegativeButton(R.string.disclaimer_negative_button, null) + .setPositiveButton(R.string.disclaimer_positive_button) { _, _ -> + openUrlInChromeCustomTab(activity, null, VectorSettingsUrls.DISCLAIMER_URL) + } + .show() + } + } + + fun doNotShowDisclaimerDialog() { sharedPrefs.edit { putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE) } - - val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_disclaimer_content, null) - - MaterialAlertDialogBuilder(activity) - .setView(dialogLayout) - .setCancelable(false) - .setNegativeButton(R.string.disclaimer_negative_button, null) - .setPositiveButton(R.string.disclaimer_positive_button) { _, _ -> - openUrlInChromeCustomTab(activity, null, VectorSettingsUrls.DISCLAIMER_URL) - } - .show() - } -} - -fun doNotShowDisclaimerDialog(context: Context) { - val sharedPrefs = DefaultSharedPreferences.getInstance(context) - - sharedPrefs.edit { - putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE) } } 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 8fb73d6571..10e8447a2b 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 @@ -56,7 +56,7 @@ import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewMode import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.crypto.recover.SetupMode -import im.vector.app.features.disclaimer.showDisclaimerDialog +import im.vector.app.features.disclaimer.DisclaimerDialog import im.vector.app.features.home.room.list.actions.RoomListSharedAction import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel import im.vector.app.features.home.room.list.home.layout.HomeLayoutSettingBottomDialogFragment @@ -141,6 +141,7 @@ class HomeActivity : @Inject lateinit var unifiedPushHelper: UnifiedPushHelper @Inject lateinit var fcmHelper: FcmHelper @Inject lateinit var nightlyProxy: NightlyProxy + @Inject lateinit var disclaimerDialog: DisclaimerDialog private var isNewAppLayoutEnabled: Boolean = false // delete once old app layout is removed @@ -570,7 +571,7 @@ class HomeActivity : .setNegativeButton(R.string.no) { _, _ -> bugReporter.deleteCrashFile() } .show() } else { - showDisclaimerDialog(this) + disclaimerDialog.showDisclaimerDialog(this) } // Force remote backup state update to update the banner if needed diff --git a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt index 4eca377e28..95e2aeedd1 100644 --- a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt +++ b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt @@ -17,28 +17,36 @@ package im.vector.app.features.homeserver import android.content.Context +import android.content.SharedPreferences import androidx.core.content.edit import im.vector.app.R -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences +import im.vector.app.core.resources.StringProvider +import javax.inject.Inject /** * Object to store and retrieve home and identity server urls. */ -object ServerUrlsRepository { +class ServerUrlsRepository @Inject constructor( + @DefaultPreferences + private val sharedPreferences: SharedPreferences, + private val stringProvider: StringProvider, +) { + companion object { + // Keys used to store default servers urls from the referrer + private const val DEFAULT_REFERRER_HOME_SERVER_URL_PREF = "default_referrer_home_server_url" + private const val DEFAULT_REFERRER_IDENTITY_SERVER_URL_PREF = "default_referrer_identity_server_url" - // Keys used to store default servers urls from the referrer - private const val DEFAULT_REFERRER_HOME_SERVER_URL_PREF = "default_referrer_home_server_url" - private const val DEFAULT_REFERRER_IDENTITY_SERVER_URL_PREF = "default_referrer_identity_server_url" - - // Keys used to store current homeserver url and identity url - const val HOME_SERVER_URL_PREF = "home_server_url" - const val IDENTITY_SERVER_URL_PREF = "identity_server_url" + // Keys used to store current homeserver url and identity url + const val HOME_SERVER_URL_PREF = "home_server_url" + const val IDENTITY_SERVER_URL_PREF = "identity_server_url" + } /** * Save home and identity sever urls received by the Referrer receiver. */ - fun setDefaultUrlsFromReferrer(context: Context, homeServerUrl: String, identityServerUrl: String) { - DefaultSharedPreferences.getInstance(context) + fun setDefaultUrlsFromReferrer(homeServerUrl: String, identityServerUrl: String) { + sharedPreferences .edit { if (homeServerUrl.isNotEmpty()) { putString(DEFAULT_REFERRER_HOME_SERVER_URL_PREF, homeServerUrl) @@ -53,8 +61,8 @@ object ServerUrlsRepository { /** * Save home and identity sever urls entered by the user. May be custom or default value. */ - fun saveServerUrls(context: Context, homeServerUrl: String, identityServerUrl: String) { - DefaultSharedPreferences.getInstance(context) + fun saveServerUrls(homeServerUrl: String, identityServerUrl: String) { + sharedPreferences .edit { putString(HOME_SERVER_URL_PREF, homeServerUrl) putString(IDENTITY_SERVER_URL_PREF, identityServerUrl) @@ -64,14 +72,12 @@ object ServerUrlsRepository { /** * Return last used homeserver url, or the default one from referrer or the default one from resources. */ - fun getLastHomeServerUrl(context: Context): String { - val prefs = DefaultSharedPreferences.getInstance(context) - - return prefs.getString( + fun getLastHomeServerUrl(): String { + return sharedPreferences.getString( HOME_SERVER_URL_PREF, - prefs.getString( + sharedPreferences.getString( DEFAULT_REFERRER_HOME_SERVER_URL_PREF, - getDefaultHomeServerUrl(context) + getDefaultHomeServerUrl() )!! )!! } @@ -79,10 +85,10 @@ object ServerUrlsRepository { /** * Return true if url is the default homeserver url form resources. */ - fun isDefaultHomeServerUrl(context: Context, url: String) = url == getDefaultHomeServerUrl(context) + fun isDefaultHomeServerUrl(context: Context, url: String) = url == getDefaultHomeServerUrl() /** * Return default homeserver url from resources. */ - fun getDefaultHomeServerUrl(context: Context): String = context.getString(R.string.matrix_org_server_url) + fun getDefaultHomeServerUrl() = stringProvider.getString(R.string.matrix_org_server_url) } diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt index 5496ff4a94..23b4fe04a8 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt @@ -16,10 +16,10 @@ package im.vector.app.features.rageshake -import android.content.Context +import android.content.SharedPreferences import android.os.Build import androidx.core.content.edit -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.resources.VersionCodeProvider import im.vector.app.features.version.VersionProvider import org.matrix.android.sdk.api.Matrix @@ -31,10 +31,11 @@ import javax.inject.Singleton @Singleton class VectorUncaughtExceptionHandler @Inject constructor( - context: Context, + @DefaultPreferences + private val preferences: SharedPreferences, private val bugReporter: BugReporter, private val versionProvider: VersionProvider, - private val versionCodeProvider: VersionCodeProvider + private val versionCodeProvider: VersionCodeProvider, ) : Thread.UncaughtExceptionHandler { // key to save the crash status @@ -44,8 +45,6 @@ class VectorUncaughtExceptionHandler @Inject constructor( private var previousHandler: Thread.UncaughtExceptionHandler? = null - private val preferences = DefaultSharedPreferences.getInstance(context) - /** * Activate this handler. */ diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt index b7caac11c3..b1a3fa9566 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt @@ -17,10 +17,11 @@ package im.vector.app.features.settings import android.content.Context +import android.content.SharedPreferences import android.content.res.Configuration import androidx.core.content.edit import im.vector.app.R -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.resources.BuildMeta import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -37,6 +38,8 @@ import javax.inject.Singleton class VectorLocale @Inject constructor( private val context: Context, private val buildMeta: BuildMeta, + @DefaultPreferences + private val preferences: SharedPreferences, ) { companion object { const val APPLICATION_LOCALE_COUNTRY_KEY = "APPLICATION_LOCALE_COUNTRY_KEY" @@ -63,8 +66,6 @@ class VectorLocale @Inject constructor( * Init this singleton. */ fun init() { - val preferences = DefaultSharedPreferences.getInstance(context) - if (preferences.contains(APPLICATION_LOCALE_LANGUAGE_KEY)) { applicationLocale = Locale( preferences.getString(APPLICATION_LOCALE_LANGUAGE_KEY, "")!!, @@ -90,7 +91,7 @@ class VectorLocale @Inject constructor( fun saveApplicationLocale(locale: Locale) { applicationLocale = locale - DefaultSharedPreferences.getInstance(context).edit { + preferences.edit { val language = locale.language if (language.isEmpty()) { remove(APPLICATION_LOCALE_LANGUAGE_KEY) 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 4da6455f74..16d3210b45 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 @@ -24,8 +24,9 @@ import androidx.annotation.BoolRes import androidx.core.content.edit import com.squareup.seismic.ShakeDetector import im.vector.app.R -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.resources.BuildMeta +import im.vector.app.core.resources.StringProvider import im.vector.app.core.time.Clock import im.vector.app.features.VectorFeatures import im.vector.app.features.disclaimer.SHARED_PREF_KEY @@ -41,6 +42,9 @@ class VectorPreferences @Inject constructor( private val clock: Clock, private val buildMeta: BuildMeta, private val vectorFeatures: VectorFeatures, + @DefaultPreferences + private val defaultPrefs: SharedPreferences, + private val stringProvider: StringProvider, ) { companion object { @@ -289,8 +293,6 @@ class VectorPreferences @Inject constructor( ) } - private val defaultPrefs = DefaultSharedPreferences.getInstance(context) - /** * Allow subscribing and unsubscribing to configuration changes. This is * particularly useful when you need to be notified of a configuration change @@ -716,10 +718,10 @@ class VectorPreferences @Inject constructor( */ fun getSelectedMediasSavingPeriodString(): String { return when (getSelectedMediasSavingPeriod()) { - MEDIA_SAVING_3_DAYS -> context.getString(R.string.media_saving_period_3_days) - MEDIA_SAVING_1_WEEK -> context.getString(R.string.media_saving_period_1_week) - MEDIA_SAVING_1_MONTH -> context.getString(R.string.media_saving_period_1_month) - MEDIA_SAVING_FOREVER -> context.getString(R.string.media_saving_period_forever) + MEDIA_SAVING_3_DAYS -> stringProvider.getString(R.string.media_saving_period_3_days) + MEDIA_SAVING_1_WEEK -> stringProvider.getString(R.string.media_saving_period_1_week) + MEDIA_SAVING_1_MONTH -> stringProvider.getString(R.string.media_saving_period_1_month) + MEDIA_SAVING_FOREVER -> stringProvider.getString(R.string.media_saving_period_forever) else -> "?" } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt index fbf54479fc..28e167779d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt @@ -23,17 +23,19 @@ import android.net.Uri import android.os.Bundle import androidx.preference.Preference import androidx.preference.SwitchPreference +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.preference.VectorPreference -import im.vector.app.core.utils.getCallRingtoneName -import im.vector.app.core.utils.getCallRingtoneUri -import im.vector.app.core.utils.setCallRingtoneUri -import im.vector.app.core.utils.setUseRiotDefaultRingtone +import im.vector.app.core.utils.RingtoneUtils import im.vector.app.features.analytics.plan.MobileScreen +import javax.inject.Inject +@AndroidEntryPoint class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() { + @Inject lateinit var ringtoneUtils: RingtoneUtils + override var titleRes = R.string.preference_voice_and_video override val preferenceXmlRes = R.xml.vector_settings_voice_video @@ -52,12 +54,12 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() { override fun bindPref() { // Incoming call sounds mUseRiotCallRingtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.let { setUseRiotDefaultRingtone(it, mUseRiotCallRingtonePreference.isChecked) } + ringtoneUtils.setUseRiotDefaultRingtone(mUseRiotCallRingtonePreference.isChecked) false } mCallRingtonePreference.let { - activity?.let { activity -> it.summary = getCallRingtoneName(activity) } + it.summary = ringtoneUtils.getCallRingtoneName() it.onPreferenceClickListener = Preference.OnPreferenceClickListener { displayRingtonePicker() false @@ -68,10 +70,9 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() { private val ringtoneStartForActivityResult = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { val callRingtoneUri: Uri? = activityResult.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) - val thisActivity = activity - if (callRingtoneUri != null && thisActivity != null) { - setCallRingtoneUri(thisActivity, callRingtoneUri) - mCallRingtonePreference.summary = getCallRingtoneName(thisActivity) + if (callRingtoneUri != null) { + ringtoneUtils.setCallRingtoneUri(callRingtoneUri) + mCallRingtonePreference.summary = ringtoneUtils.getCallRingtoneName() } } } @@ -82,7 +83,7 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() { putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false) putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true) putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE) - activity?.let { putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, getCallRingtoneUri(it)) } + putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUtils.getCallRingtoneUri()) } ringtoneStartForActivityResult.launch(intent) } diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt index 96af7906e2..b5c7b162d8 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt @@ -27,8 +27,8 @@ import androidx.annotation.ColorInt import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.core.graphics.drawable.DrawableCompat +import androidx.preference.PreferenceManager import im.vector.app.R -import im.vector.app.core.di.DefaultSharedPreferences import timber.log.Timber import java.util.concurrent.atomic.AtomicReference @@ -84,7 +84,7 @@ object ThemeUtils { fun getApplicationTheme(context: Context): String { val currentTheme = this.currentTheme.get() return if (currentTheme == null) { - val prefs = DefaultSharedPreferences.getInstance(context) + val prefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, DEFAULT_THEME) ?: DEFAULT_THEME if (themeFromPref == "status") { // Migrate to the default theme From b4494ee8eafa84879a66e28cbdeca91fc2faa8fe Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 16:41:00 +0200 Subject: [PATCH 08/17] Remove `DefaultSharedPreferences` since we now have @DefaultPreferences which provide a singleton. Some fun has been moved to injectable classes due to this change. Not compiling, still work to do, but I prefer to split into 2 separate commits. --- .../im/vector/app/features/homeserver/ServerUrlsRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt index 95e2aeedd1..65bf24dc45 100644 --- a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt +++ b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt @@ -85,7 +85,7 @@ class ServerUrlsRepository @Inject constructor( /** * Return true if url is the default homeserver url form resources. */ - fun isDefaultHomeServerUrl(context: Context, url: String) = url == getDefaultHomeServerUrl() + fun isDefaultHomeServerUrl(url: String) = url == getDefaultHomeServerUrl() /** * Return default homeserver url from resources. From c735ea5e3de53627d4c7d459bae837904d25b9ec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 16:49:07 +0200 Subject: [PATCH 09/17] Remove duplication between `KeysBackupBanner.State` and `ServerBackupStatusViewModel.BannerState` and move the some logic to the ViewModel --- .../app/core/ui/views/KeysBackupBanner.kt | 116 +++--------------- .../restore/KeysBackupRestoreActivity.kt | 8 +- .../app/features/home/HomeDetailFragment.kt | 15 ++- .../features/home/NewHomeDetailFragment.kt | 15 ++- .../signout/ServerBackupStatusAction.kt | 25 ++++ .../signout/ServerBackupStatusViewModel.kt | 93 ++++++++++++-- 6 files changed, 156 insertions(+), 116 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusAction.kt diff --git a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt index a753139c66..f0a42dd78d 100755 --- a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt @@ -20,10 +20,10 @@ import android.content.Context import android.util.AttributeSet import android.view.View import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.content.edit import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.databinding.ViewKeysBackupBannerBinding +import im.vector.app.features.workers.signout.BannerState import timber.log.Timber /** @@ -37,16 +37,12 @@ class KeysBackupBanner @JvmOverloads constructor( ) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener { var delegate: Delegate? = null - private var state: State = State.Initial + private var state: BannerState = BannerState.Initial private lateinit var views: ViewKeysBackupBannerBinding init { setupView() - DefaultSharedPreferences.getInstance(context).edit { - putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false) - putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, "") - } } /** @@ -55,7 +51,7 @@ class KeysBackupBanner @JvmOverloads constructor( * @param newState the newState representing the view * @param force true to force the rendering of the view */ - fun render(newState: State, force: Boolean = false) { + fun render(newState: BannerState, force: Boolean = false) { if (newState == state && !force) { Timber.v("State unchanged") return @@ -66,48 +62,26 @@ class KeysBackupBanner @JvmOverloads constructor( hideAll() when (newState) { - State.Initial -> renderInitial() - State.Hidden -> renderHidden() - is State.Setup -> renderSetup(newState.numberOfKeys) - is State.Recover -> renderRecover(newState.version) - is State.Update -> renderUpdate(newState.version) - State.BackingUp -> renderBackingUp() + BannerState.Initial -> renderInitial() + BannerState.Hidden -> renderHidden() + is BannerState.Setup -> renderSetup(newState) + is BannerState.Recover -> renderRecover(newState) + is BannerState.Update -> renderUpdate(newState) + BannerState.BackingUp -> renderBackingUp() } } override fun onClick(v: View?) { when (state) { - is State.Setup -> delegate?.setupKeysBackup() - is State.Update, - is State.Recover -> delegate?.recoverKeysBackup() + is BannerState.Setup -> delegate?.setupKeysBackup() + is BannerState.Update, + is BannerState.Recover -> delegate?.recoverKeysBackup() else -> Unit } } private fun onCloseClicked() { - state.let { - when (it) { - is State.Setup -> { - DefaultSharedPreferences.getInstance(context).edit { - putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, true) - } - } - is State.Recover -> { - DefaultSharedPreferences.getInstance(context).edit { - putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, it.version) - } - } - is State.Update -> { - DefaultSharedPreferences.getInstance(context).edit { - putString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, it.version) - } - } - else -> { - // Should not happen, close button is not displayed in other cases - } - } - } - + delegate?.onCloseClicked() // Force refresh render(state, true) } @@ -132,9 +106,8 @@ class KeysBackupBanner @JvmOverloads constructor( isVisible = false } - private fun renderSetup(nbOfKeys: Int) { - if (nbOfKeys == 0 || - DefaultSharedPreferences.getInstance(context).getBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)) { + private fun renderSetup(state: BannerState.Setup) { + if (state.numberOfKeys == 0 || state.doNotShowAgain) { // Do not display the setup banner if there is no keys to backup, or if the user has already closed it isVisible = false } else { @@ -147,8 +120,8 @@ class KeysBackupBanner @JvmOverloads constructor( } } - private fun renderRecover(version: String) { - if (version == DefaultSharedPreferences.getInstance(context).getString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, null)) { + private fun renderRecover(state: BannerState.Recover) { + if (state.version == state.doNotShowForVersion) { isVisible = false } else { isVisible = true @@ -160,8 +133,8 @@ class KeysBackupBanner @JvmOverloads constructor( } } - private fun renderUpdate(version: String) { - if (version == DefaultSharedPreferences.getInstance(context).getString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, null)) { + private fun renderUpdate(state: BannerState.Update) { + if (state.version == state.doNotShowForVersion) { isVisible = false } else { isVisible = true @@ -190,61 +163,12 @@ class KeysBackupBanner @JvmOverloads constructor( views.viewKeysBackupBannerLoading.isVisible = false } - /** - * The state representing the view. - * It can take one state at a time. - */ - sealed class State { - // Not yet rendered - object Initial : State() - - // View will be Gone - object Hidden : State() - - // Keys backup is not setup, numberOfKeys is the number of locally stored keys - data class Setup(val numberOfKeys: Int) : State() - - // Keys backup can be recovered, with version from the server - data class Recover(val version: String) : State() - - // Keys backup can be updated - data class Update(val version: String) : State() - - // Keys are backing up - object BackingUp : State() - } - /** * An interface to delegate some actions to another object. */ interface Delegate { + fun onCloseClicked() fun setupKeysBackup() fun recoverKeysBackup() } - - companion object { - /** - * Preference key for setup. Value is a boolean. - */ - private const val BANNER_SETUP_DO_NOT_SHOW_AGAIN = "BANNER_SETUP_DO_NOT_SHOW_AGAIN" - - /** - * Preference key for recover. Value is a backup version (String). - */ - private const val BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION = "BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION" - - /** - * Preference key for update. Value is a backup version (String). - */ - private const val BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION = "BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION" - - /** - * Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version. - */ - fun onRecoverDoneForVersion(context: Context, version: String) { - DefaultSharedPreferences.getInstance(context).edit { - putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, version) - } - } - } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index 3089481255..c6e86f6f6b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -18,6 +18,7 @@ package im.vector.app.features.crypto.keysbackup.restore import android.app.Activity import android.content.Context import android.content.Intent +import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R @@ -27,8 +28,9 @@ import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.SimpleFragmentActivity -import im.vector.app.core.ui.views.KeysBackupBanner import im.vector.app.features.crypto.quads.SharedSecureStorageActivity +import im.vector.app.features.workers.signout.ServerBackupStatusAction +import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import javax.inject.Inject @@ -46,6 +48,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { override fun getTitleRes() = R.string.title_activity_keys_backup_restore private lateinit var viewModel: KeysBackupRestoreSharedViewModel + private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() override fun onBackPressed() { hideWaitingView() @@ -95,7 +98,8 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { } KeysBackupRestoreSharedViewModel.NAVIGATE_TO_SUCCESS -> { viewModel.keyVersionResult.value?.version?.let { - KeysBackupBanner.onRecoverDoneForVersion(this, it) + // Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version. + serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnRecoverDoneForVersion(it)) } replaceFragment(views.container, KeysBackupRestoreSuccessFragment::class.java, allowStateLoss = true) } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index a2544a2fde..e824dc1820 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -56,6 +56,7 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.BannerState +import im.vector.app.features.workers.signout.ServerBackupStatusAction import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -289,13 +290,15 @@ class HomeDetailFragment : } private fun setupKeysBackupBanner() { + serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerDisplayed) serverBackupStatusViewModel .onEach { when (val banState = it.bannerState.invoke()) { - is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false) - BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false) - null, - BannerState.Hidden -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false) + is BannerState.Setup, + BannerState.BackingUp, + BannerState.Hidden -> views.homeKeysBackupBanner.render(banState, false) + null -> views.homeKeysBackupBanner.render(BannerState.Hidden, false) + else -> Unit /* No op? */ } } views.homeKeysBackupBanner.delegate = this @@ -402,6 +405,10 @@ class HomeDetailFragment : * KeysBackupBanner Listener * ========================================================================================== */ + override fun onCloseClicked() { + serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed) + } + override fun setupKeysBackup() { navigator.openKeysBackupSetup(requireActivity(), false) } diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index f31f8a7d92..66bb9ef876 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -57,6 +57,7 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS import im.vector.app.features.spaces.SpaceListBottomSheet import im.vector.app.features.workers.signout.BannerState +import im.vector.app.features.workers.signout.ServerBackupStatusAction import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -300,13 +301,15 @@ class NewHomeDetailFragment : } private fun setupKeysBackupBanner() { + serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerDisplayed) serverBackupStatusViewModel .onEach { when (val banState = it.bannerState.invoke()) { - is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false) - BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false) - null, - BannerState.Hidden -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false) + is BannerState.Setup, + BannerState.BackingUp, + BannerState.Hidden -> views.homeKeysBackupBanner.render(banState, false) + null -> views.homeKeysBackupBanner.render(BannerState.Hidden, false) + else -> Unit /* No op? */ } } views.homeKeysBackupBanner.delegate = this @@ -348,6 +351,10 @@ class NewHomeDetailFragment : * KeysBackupBanner Listener * ========================================================================================== */ + override fun onCloseClicked() { + serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed) + } + override fun setupKeysBackup() { navigator.openKeysBackupSetup(requireActivity(), false) } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusAction.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusAction.kt new file mode 100644 index 0000000000..2c59a80964 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusAction.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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.features.workers.signout + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface ServerBackupStatusAction : VectorViewModelAction { + data class OnRecoverDoneForVersion(val version: String) : ServerBackupStatusAction + object OnBannerDisplayed : ServerBackupStatusAction + object OnBannerClosed : ServerBackupStatusAction +} diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index d2a4b3193a..81cf48a832 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -16,6 +16,8 @@ package im.vector.app.features.workers.signout +import android.content.SharedPreferences +import androidx.core.content.edit import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState @@ -24,9 +26,9 @@ import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -51,29 +53,55 @@ data class ServerBackupStatusViewState( * The state representing the view. * It can take one state at a time. */ -sealed class BannerState { +sealed interface BannerState { + // Not yet rendered + object Initial : BannerState - object Hidden : BannerState() + // View will be Gone + object Hidden : BannerState // Keys backup is not setup, numberOfKeys is the number of locally stored keys - data class Setup(val numberOfKeys: Int) : BannerState() + data class Setup(val numberOfKeys: Int, val doNotShowAgain: Boolean) : BannerState + + // Keys backup can be recovered, with version from the server + data class Recover(val version: String, val doNotShowForVersion: String) : BannerState + + // Keys backup can be updated + data class Update(val version: String, val doNotShowForVersion: String) : BannerState // Keys are backing up - object BackingUp : BannerState() + object BackingUp : BannerState } class ServerBackupStatusViewModel @AssistedInject constructor( @Assisted initialState: ServerBackupStatusViewState, - private val session: Session + private val session: Session, + @DefaultPreferences + private val sharedPreferences: SharedPreferences, ) : - VectorViewModel(initialState), KeysBackupStateListener { + VectorViewModel(initialState), KeysBackupStateListener { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { override fun create(initialState: ServerBackupStatusViewState): ServerBackupStatusViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + /** + * Preference key for setup. Value is a boolean. + */ + private const val BANNER_SETUP_DO_NOT_SHOW_AGAIN = "BANNER_SETUP_DO_NOT_SHOW_AGAIN" + + /** + * Preference key for recover. Value is a backup version (String). + */ + private const val BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION = "BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION" + + /** + * Preference key for update. Value is a backup version (String). + */ + private const val BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION = "BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION" + } // Keys exported manually val keysExportedToFile = MutableLiveData() @@ -105,7 +133,10 @@ class ServerBackupStatusViewModel @AssistedInject constructor( pInfo.getOrNull()?.allKnown().orFalse()) ) { // So 4S is not setup and we have local secrets, - return@combine BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup()) + return@combine BannerState.Setup( + numberOfKeys = getNumberOfKeysToBackup(), + doNotShowAgain = sharedPreferences.getBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false) + ) } BannerState.Hidden } @@ -161,5 +192,47 @@ class ServerBackupStatusViewModel @AssistedInject constructor( } } - override fun handle(action: EmptyAction) {} + override fun handle(action: ServerBackupStatusAction) { + when (action) { + is ServerBackupStatusAction.OnRecoverDoneForVersion -> handleOnRecoverDoneForVersion(action) + ServerBackupStatusAction.OnBannerDisplayed -> handleOnBannerDisplayed() + ServerBackupStatusAction.OnBannerClosed -> handleOnBannerClosed() + } + } + + private fun handleOnRecoverDoneForVersion(action: ServerBackupStatusAction.OnRecoverDoneForVersion) { + sharedPreferences.edit { + putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, action.version) + } + } + + private fun handleOnBannerDisplayed() { + sharedPreferences.edit { + putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false) + putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, "") + } + } + + private fun handleOnBannerClosed() = withState { state -> + when (val bannerState = state.bannerState()) { + is BannerState.Setup -> { + sharedPreferences.edit { + putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, true) + } + } + is BannerState.Recover -> { + sharedPreferences.edit { + putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, bannerState.version) + } + } + is BannerState.Update -> { + sharedPreferences.edit { + putString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, bannerState.version) + } + } + else -> { + // Should not happen, close button is not displayed in other cases + } + } + } } From 226672378959e970db25a09f418fa6653a8dd5c8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 17:03:46 +0200 Subject: [PATCH 10/17] Remove `DefaultSharedPreferences` since we now have @DefaultPreferences which provide a singleton. --- .../app/receivers/VectorDebugReceiver.kt | 19 +++++++++++-------- .../im/vector/app/push/fcm/GoogleFcmHelper.kt | 8 ++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/vector-app/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt b/vector-app/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt index 550dc055d9..4edbdd0591 100644 --- a/vector-app/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt +++ b/vector-app/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt @@ -23,7 +23,7 @@ import android.content.IntentFilter import android.content.SharedPreferences import androidx.core.content.edit import im.vector.app.core.debug.DebugReceiver -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.utils.lsFiles import timber.log.Timber import javax.inject.Inject @@ -31,7 +31,10 @@ import javax.inject.Inject /** * Receiver to handle some command from ADB */ -class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugReceiver { +class VectorDebugReceiver @Inject constructor( + @DefaultPreferences + private val sharedPreferences: SharedPreferences, +) : BroadcastReceiver(), DebugReceiver { override fun register(context: Context) { context.registerReceiver(this, getIntentFilter(context)) @@ -47,14 +50,14 @@ class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugRece intent.action?.let { when { it.endsWith(DEBUG_ACTION_DUMP_FILESYSTEM) -> lsFiles(context) - it.endsWith(DEBUG_ACTION_DUMP_PREFERENCES) -> dumpPreferences(context) - it.endsWith(DEBUG_ACTION_ALTER_SCALAR_TOKEN) -> alterScalarToken(context) + it.endsWith(DEBUG_ACTION_DUMP_PREFERENCES) -> dumpPreferences() + it.endsWith(DEBUG_ACTION_ALTER_SCALAR_TOKEN) -> alterScalarToken() } } } - private fun dumpPreferences(context: Context) { - logPrefs("DefaultSharedPreferences", DefaultSharedPreferences.getInstance(context)) + private fun dumpPreferences() { + logPrefs("DefaultSharedPreferences", sharedPreferences) } private fun logPrefs(name: String, sharedPreferences: SharedPreferences?) { @@ -67,8 +70,8 @@ class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugRece } } - private fun alterScalarToken(context: Context) { - DefaultSharedPreferences.getInstance(context).edit { + private fun alterScalarToken() { + sharedPreferences.edit { // putString("SCALAR_TOKEN_PREFERENCE_KEY" + Matrix.getInstance(context).defaultSession.myUserId, "bad_token") } } diff --git a/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt b/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt index d64847c124..7cf90cf874 100755 --- a/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt +++ b/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt @@ -17,6 +17,7 @@ package im.vector.app.push.fcm import android.app.Activity import android.content.Context +import android.content.SharedPreferences import android.widget.Toast import androidx.core.content.edit import com.google.android.gms.common.ConnectionResult @@ -24,7 +25,7 @@ import com.google.android.gms.common.GoogleApiAvailability import com.google.firebase.messaging.FirebaseMessaging import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import timber.log.Timber @@ -35,14 +36,13 @@ import javax.inject.Inject * It has an alter ego in the fdroid variant. */ class GoogleFcmHelper @Inject constructor( - context: Context, + @DefaultPreferences + private val sharedPrefs: SharedPreferences, ) : FcmHelper { companion object { private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" } - private val sharedPrefs = DefaultSharedPreferences.getInstance(context) - override fun isFirebaseAvailable(): Boolean = true override fun getFcmToken(): String? { From 2bda97224cbf174759f4e81e1aa79f57db1dc7ed Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 17:15:43 +0200 Subject: [PATCH 11/17] Cleanup --- .../im/vector/app/features/homeserver/ServerUrlsRepository.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt index 65bf24dc45..636c557da9 100644 --- a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt +++ b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt @@ -16,7 +16,6 @@ package im.vector.app.features.homeserver -import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit import im.vector.app.R From 0910b118ffa33b13421f017d356c2a9d8ba94671 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 17:20:03 +0200 Subject: [PATCH 12/17] Changelog --- changelog.d/7159.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7159.misc diff --git a/changelog.d/7159.misc b/changelog.d/7159.misc new file mode 100644 index 0000000000..76f5f45c40 --- /dev/null +++ b/changelog.d/7159.misc @@ -0,0 +1 @@ +Fix lint warning, and cleanup the code From 5886245bbffb6a77be4c864610b6f5ab305ca80b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 17:35:26 +0200 Subject: [PATCH 13/17] Reverse condition for code clarity with `ChecksSdkIntAtLeast` --- .../app/features/voice/VoiceRecorderProvider.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt index c024e0c6d4..05e537b2b0 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt @@ -33,18 +33,18 @@ class VoiceRecorderProvider @Inject constructor( private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, ) { fun provideVoiceRecorder(): VoiceRecorder { - return if (useFallbackRecorder()) { - VoiceRecorderL(context, Dispatchers.IO) - } else { + return if (useNativeRecorder()) { VoiceRecorderQ(context) + } else { + VoiceRecorderL(context, Dispatchers.IO) } } - @ChecksSdkIntAtLeast(api = 29) - private fun useFallbackRecorder(): Boolean { - return buildVersionSdkIntProvider.get() < Build.VERSION_CODES.Q || - !hasOpusEncoder() || - vectorFeatures.forceUsageOfOpusEncoder() + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q) + private fun useNativeRecorder(): Boolean { + return buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.Q && + hasOpusEncoder() && + !vectorFeatures.forceUsageOfOpusEncoder() } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) From 658a09ea6aa24464b75646f0e82cca51d85d1c5a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 17:50:17 +0200 Subject: [PATCH 14/17] No need to use `@SuppressLint("NewApi")` when `@ChecksSdkIntAtLeast` is used. --- .../matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt | 3 +++ .../features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt index acbf9ca061..c8c328c92c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt @@ -34,4 +34,7 @@ interface BuildVersionSdkIntProvider { result() } else null } + + @ChecksSdkIntAtLeast(parameter = 0) + fun isAtLeast(version: Int) = get() >= version } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt index bb55ceb1b7..84e98785f4 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt @@ -36,14 +36,13 @@ class LockScreenKeysMigrator @Inject constructor( /** * Performs any needed migrations in order. */ - @SuppressLint("NewApi") suspend fun migrateIfNeeded() { if (legacyPinCodeMigrator.isMigrationNeeded()) { legacyPinCodeMigrator.migrate() missingSystemKeyMigrator.migrateIfNeeded() } - if (systemKeyV1Migrator.isMigrationNeeded() && versionProvider.get() >= Build.VERSION_CODES.M) { + if (systemKeyV1Migrator.isMigrationNeeded() && versionProvider.isAtLeast(Build.VERSION_CODES.M)) { systemKeyV1Migrator.migrate() } } From 7f5c712e88b47261591408ac1009dba78ca47cfe Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 18:02:40 +0200 Subject: [PATCH 15/17] No need to use `@SuppressLint("NewApi")` when `@ChecksSdkIntAtLeast` is used - more cleanup --- .../api/securestorage/SecretStoringUtils.kt | 7 ++----- .../im/vector/app/core/extensions/Context.kt | 4 +--- .../VectorActivityLifecycleCallbacks.kt | 2 -- .../features/login/LoginCaptchaFragment.kt | 1 - .../notifications/NotificationUtils.kt | 3 --- .../onboarding/ftueauth/CaptchaWebview.kt | 1 - .../lockscreen/biometrics/BiometricHelper.kt | 2 -- .../pin/lockscreen/crypto/KeyStoreCrypto.kt | 5 +---- .../crypto/LockScreenKeysMigrator.kt | 1 - .../migrations/MissingSystemKeyMigrator.kt | 5 ++--- .../pin/lockscreen/ui/LockScreenViewModel.kt | 20 +++++++++---------- .../VectorSettingsSecurityPrivacyFragment.kt | 2 -- .../features/widgets/webview/WidgetWebView.kt | 2 -- 13 files changed, 15 insertions(+), 40 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt index e701e0f3ba..234a8eee98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt @@ -131,11 +131,10 @@ class SecretStoringUtils @Inject constructor( * * The secret is encrypted using the following method: AES/GCM/NoPadding */ - @SuppressLint("NewApi") @Throws(Exception::class) fun securelyStoreBytes(secret: ByteArray, keyAlias: String): ByteArray { return when { - buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptBytesM(secret, keyAlias) + buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) -> encryptBytesM(secret, keyAlias) else -> encryptBytes(secret, keyAlias) } } @@ -156,10 +155,9 @@ class SecretStoringUtils @Inject constructor( } } - @SuppressLint("NewApi") fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) { when { - buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any) + buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) -> saveSecureObjectM(keyAlias, output, any) else -> saveSecureObject(keyAlias, output, any) } } @@ -189,7 +187,6 @@ class SecretStoringUtils @Inject constructor( return cipher } - @SuppressLint("NewApi") @RequiresApi(Build.VERSION_CODES.M) private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey { val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt index 1ed5aa8ba1..2e3e8c9306 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt @@ -16,7 +16,6 @@ package im.vector.app.core.extensions -import android.annotation.SuppressLint import android.content.Context import android.graphics.drawable.Drawable import android.net.ConnectivityManager @@ -91,10 +90,9 @@ fun Context.safeOpenOutputStream(uri: Uri): OutputStream? { * * @return true if no active connection is found */ -@SuppressLint("NewApi") // false positive fun Context.inferNoConnectivity(sdkIntProvider: BuildVersionSdkIntProvider): Boolean { val connectivityManager = getSystemService()!! - return if (sdkIntProvider.get() > Build.VERSION_CODES.M) { + return if (sdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) { val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) when { networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false diff --git a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt index 75f02c36d7..c884843f5c 100644 --- a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt +++ b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt @@ -16,7 +16,6 @@ package im.vector.app.features.lifecycle -import android.annotation.SuppressLint import android.app.Activity import android.app.ActivityManager import android.app.Application @@ -91,7 +90,6 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager * * @return true if an app task is corrupted by a potentially malicious activity */ - @SuppressLint("NewApi") private suspend fun isTaskCorrupted(activity: Activity): Boolean = withContext(Dispatchers.Default) { val context = activity.applicationContext val packageManager: PackageManager = context.packageManager diff --git a/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt index 25403b06f3..b082e37933 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt @@ -144,7 +144,6 @@ class LoginCaptchaFragment : // runOnUiThread(Runnable { finish() }) } - @SuppressLint("NewApi") override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { super.onReceivedHttpError(view, request, errorResponse) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index cef94b5db2..7e91a89603 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -218,7 +218,6 @@ class NotificationUtils @Inject constructor( * @param withProgress true to show indeterminate progress on the notification * @return the polling thread listener notification */ - @SuppressLint("NewApi") fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification { // build the pending intent go to the home screen if this is clicked. val i = HomeActivity.newIntent(context, firstStartMainActivity = false) @@ -287,7 +286,6 @@ class NotificationUtils @Inject constructor( * @param fromBg true if the app is in background when posting the notification * @return the call notification. */ - @SuppressLint("NewApi") fun buildIncomingCallNotification( call: WebRtcCall, title: String, @@ -420,7 +418,6 @@ class NotificationUtils @Inject constructor( * @param title title of the notification * @return the call notification. */ - @SuppressLint("NewApi") fun buildPendingCallNotification( call: WebRtcCall, title: String diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt index 23c6c13b5e..11f257c4e8 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt @@ -92,7 +92,6 @@ class CaptchaWebview @Inject constructor( Timber.e("## onError() : $errorMessage") } - @SuppressLint("NewApi") override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { super.onReceivedHttpError(view, request, errorResponse) when { diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt index 9bcf6e4264..026ee159ed 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt @@ -16,7 +16,6 @@ package im.vector.app.features.pin.lockscreen.biometrics -import android.annotation.SuppressLint import android.content.Context import android.os.Build import androidx.annotation.MainThread @@ -156,7 +155,6 @@ class BiometricHelper @AssistedInject constructor( return authenticate(activity) } - @SuppressLint("NewApi") @OptIn(ExperimentalCoroutinesApi::class) private fun authenticateInternal( activity: FragmentActivity, diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt index a42ce3a9b7..fd676f1662 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt @@ -16,7 +16,6 @@ package im.vector.app.features.pin.lockscreen.crypto -import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException @@ -55,7 +54,6 @@ class KeyStoreCrypto @AssistedInject constructor( * Ensures a [Key] for the [alias] exists and validates it. * @throws KeyPermanentlyInvalidatedException if key is not valid. */ - @SuppressLint("NewApi") @Throws(KeyPermanentlyInvalidatedException::class) fun ensureKey() = secretStoringUtils.ensureKey(alias).also { // Check validity of Key by initializing an encryption Cipher @@ -109,10 +107,9 @@ class KeyStoreCrypto @AssistedInject constructor( /** * Check if the key associated with the [alias] is valid. */ - @SuppressLint("NewApi") fun hasValidKey(): Boolean { val keyExists = hasKey() - return if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && keyExists) { + return if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) && keyExists) { val initializedKey = tryOrNull("Error validating lockscreen system key.") { ensureKey() } initializedKey != null } else { diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt index 84e98785f4..c2d70a3734 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt @@ -16,7 +16,6 @@ package im.vector.app.features.pin.lockscreen.crypto -import android.annotation.SuppressLint import android.os.Build import im.vector.app.features.pin.lockscreen.crypto.migrations.LegacyPinCodeMigrator import im.vector.app.features.pin.lockscreen.crypto.migrations.MissingSystemKeyMigrator diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt index 4c33c14954..7593aa6de3 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt @@ -16,7 +16,6 @@ package im.vector.app.features.pin.lockscreen.crypto.migrations -import android.annotation.SuppressLint import android.os.Build import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto import im.vector.app.features.pin.lockscreen.di.BiometricKeyAlias @@ -38,9 +37,9 @@ class MissingSystemKeyMigrator @Inject constructor( /** * If user had biometric auth enabled, ensure system key exists, creating one if needed. */ - @SuppressLint("NewApi") fun migrateIfNeeded() { - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && vectorPreferences.useBiometricsToUnlock()) { + if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) && + vectorPreferences.useBiometricsToUnlock()) { val systemKeyStoreCrypto = keystoreCryptoFactory.provide(systemKeyAlias, true) runCatching { systemKeyStoreCrypto.ensureKey() diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt index 33ea590f1d..87d3f93f9b 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.pin.lockscreen.ui -import android.annotation.SuppressLint import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException @@ -139,12 +138,12 @@ class LockScreenViewModel @AssistedInject constructor( } }.launchIn(viewModelScope) - @SuppressLint("NewApi") private fun showBiometricPrompt(activity: FragmentActivity) = flow { emitAll(biometricHelper.authenticate(activity)) }.catch { error -> when { - versionProvider.get() >= Build.VERSION_CODES.M && error is KeyPermanentlyInvalidatedException -> { + versionProvider.isAtLeast(Build.VERSION_CODES.M) && + error is KeyPermanentlyInvalidatedException -> { onBiometricKeyInvalidated() } else -> { @@ -168,15 +167,14 @@ class LockScreenViewModel @AssistedInject constructor( _viewEvents.post(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) } - @SuppressLint("NewApi") private suspend fun updateStateWithBiometricInfo() { // This is a terrible hack, but I found no other way to ensure this would be called only after the device is considered unlocked on Android 12+ waitUntilKeyguardIsUnlocked() setState { val isBiometricKeyInvalidated = biometricHelper.hasSystemKey && !biometricHelper.isSystemKeyValid val canUseBiometricAuth = lockScreenConfiguration.mode == LockScreenMode.VERIFY && - !isSystemAuthTemporarilyDisabledByBiometricPrompt && - biometricHelper.isSystemAuthEnabledAndValid + !isSystemAuthTemporarilyDisabledByBiometricPrompt && + biometricHelper.isSystemAuthEnabledAndValid val showBiometricPromptAutomatically = canUseBiometricAuth && lockScreenConfiguration.autoStartBiometric copy( canUseBiometricAuth = canUseBiometricAuth, @@ -191,12 +189,12 @@ class LockScreenViewModel @AssistedInject constructor( * after an Activity's `onResume` method. If we mix that with the system keys needing the device to be unlocked before they're used, we get crashes. * See issue [#6768](https://github.com/vector-im/element-android/issues/6768). */ - @SuppressLint("NewApi") private suspend fun waitUntilKeyguardIsUnlocked() { - if (versionProvider.get() < Build.VERSION_CODES.S) return - withTimeoutOrNull(5.seconds) { - while (keyguardManager.isDeviceLocked) { - delay(50.milliseconds) + if (versionProvider.isAtLeast(Build.VERSION_CODES.S)) { + withTimeoutOrNull(5.seconds) { + while (keyguardManager.isDeviceLocked) { + delay(50.milliseconds) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index ecb1779a4a..5cbdf114a5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -17,7 +17,6 @@ package im.vector.app.features.settings -import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.net.Uri @@ -448,7 +447,6 @@ class VectorSettingsSecurityPrivacyFragment : /** * Manage the e2e keys import. */ - @SuppressLint("NewApi") private fun importKeys() { openFileSelection( requireActivity(), diff --git a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt index ac9930866f..254a7f97f5 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt @@ -16,7 +16,6 @@ package im.vector.app.features.widgets.webview -import android.annotation.SuppressLint import android.app.Activity import android.view.ViewGroup import android.webkit.CookieManager @@ -29,7 +28,6 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.webview.VectorWebViewClient import im.vector.app.features.webview.WebEventListener -@SuppressLint("NewApi") fun WebView.setupForWidget(activity: Activity, checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase, eventListener: WebEventListener, From 0559911f39e7cf537b1c3cdaad6b207d1699478d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 22:53:32 +0200 Subject: [PATCH 16/17] Fix non passing tests --- .../app/TestBuildVersionSdkIntProvider.kt | 4 +--- .../migrations/LegacyPinCodeMigratorTests.kt | 23 +++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/TestBuildVersionSdkIntProvider.kt b/vector/src/androidTest/java/im/vector/app/TestBuildVersionSdkIntProvider.kt index ddf89b5e46..b3f9c65800 100644 --- a/vector/src/androidTest/java/im/vector/app/TestBuildVersionSdkIntProvider.kt +++ b/vector/src/androidTest/java/im/vector/app/TestBuildVersionSdkIntProvider.kt @@ -18,8 +18,6 @@ package im.vector.app import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider -class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider { - var value: Int = 0 - +class TestBuildVersionSdkIntProvider(var value: Int = 0) : BuildVersionSdkIntProvider { override fun get() = value } diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt index 44c5db89c8..5f1ba8876a 100644 --- a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt @@ -25,6 +25,7 @@ import android.security.keystore.KeyProperties import android.util.Base64 import androidx.preference.PreferenceManager import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.TestBuildVersionSdkIntProvider import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.SharedPrefPinCodeStore import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants.ANDROID_KEY_STORE @@ -32,7 +33,6 @@ import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants.LE import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every -import io.mockk.mockk import io.mockk.spyk import io.mockk.verify import kotlinx.coroutines.runBlocking @@ -42,7 +42,6 @@ import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Test import org.matrix.android.sdk.api.securestorage.SecretStoringUtils -import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import java.math.BigInteger import java.security.KeyFactory import java.security.KeyPairGenerator @@ -66,9 +65,7 @@ class LegacyPinCodeMigratorTests { SharedPrefPinCodeStore(PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().context)) ) private val keyStore: KeyStore = spyk(KeyStore.getInstance(ANDROID_KEY_STORE)).also { it.load(null) } - private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider = mockk { - every { get() } returns Build.VERSION_CODES.M - } + private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider(Build.VERSION_CODES.M) private val secretStoringUtils: SecretStoringUtils = spyk( SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider) ) @@ -126,6 +123,7 @@ class LegacyPinCodeMigratorTests { @Test fun migratePinCodeM() = runTest { val pinCode = "1234" + buildVersionSdkIntProvider.value = Build.VERSION_CODES.M saveLegacyPinCode(pinCode) legacyPinCodeMigrator.migrate() @@ -144,7 +142,7 @@ class LegacyPinCodeMigratorTests { @Test fun migratePinCodeL() = runTest { val pinCode = "1234" - every { buildVersionSdkIntProvider.get() } returns Build.VERSION_CODES.LOLLIPOP + buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP saveLegacyPinCode(pinCode) legacyPinCodeMigrator.migrate() @@ -163,7 +161,7 @@ class LegacyPinCodeMigratorTests { private fun generateLegacyKey() { if (keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS)) return - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) { + if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) { generateLegacyKeyM() } else { generateLegacyKeyL() @@ -206,7 +204,7 @@ class LegacyPinCodeMigratorTests { generateLegacyKey() val publicKey = keyStore.getCertificate(LEGACY_PIN_CODE_KEY_ALIAS).publicKey val cipher = getLegacyCipher() - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) { + if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) { val unrestrictedKey = KeyFactory.getInstance(publicKey.algorithm).generatePublic(X509EncodedKeySpec(publicKey.encoded)) val spec = OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT) cipher.init(Cipher.ENCRYPT_MODE, unrestrictedKey, spec) @@ -219,14 +217,15 @@ class LegacyPinCodeMigratorTests { } private fun getLegacyCipher(): Cipher { - return when (buildVersionSdkIntProvider.get()) { - Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.LOLLIPOP_MR1 -> getCipherL() - else -> getCipherM() + return if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) { + getCipherM() + } else { + getCipherL() } } private fun getCipherL(): Cipher { - val provider = if (buildVersionSdkIntProvider.get() < Build.VERSION_CODES.M) "AndroidOpenSSL" else "AndroidKeyStoreBCWorkaround" + val provider = if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) "AndroidKeyStoreBCWorkaround" else "AndroidOpenSSL" val transformation = "RSA/ECB/PKCS1Padding" return Cipher.getInstance(transformation, provider) } From 3600e374f2a20f3536e228ab312fb7025d563e30 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 22:54:49 +0200 Subject: [PATCH 17/17] Avoid code duplication --- .../migrations/LegacyPinCodeMigratorTests.kt | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt index 5f1ba8876a..8c50806fd9 100644 --- a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LegacyPinCodeMigratorTests.kt @@ -122,27 +122,18 @@ class LegacyPinCodeMigratorTests { @Test fun migratePinCodeM() = runTest { - val pinCode = "1234" buildVersionSdkIntProvider.value = Build.VERSION_CODES.M - saveLegacyPinCode(pinCode) - - legacyPinCodeMigrator.migrate() - - coVerify { legacyPinCodeMigrator.getDecryptedPinCode() } - verify { secretStoringUtils.securelyStoreBytes(any(), any()) } - coVerify { pinCodeStore.savePinCode(any()) } - verify { keyStore.deleteEntry(LEGACY_PIN_CODE_KEY_ALIAS) } - - val decodedPinCode = String(secretStoringUtils.loadSecureSecretBytes(Base64.decode(pinCodeStore.getPinCode().orEmpty(), Base64.NO_WRAP), alias)) - decodedPinCode shouldBeEqualTo pinCode - keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS) shouldBe false - keyStore.containsAlias(alias) shouldBe true + migratePinCode() } @Test fun migratePinCodeL() = runTest { - val pinCode = "1234" buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP + migratePinCode() + } + + private suspend fun migratePinCode() { + val pinCode = "1234" saveLegacyPinCode(pinCode) legacyPinCodeMigrator.migrate()