Merge pull request #7159 from vector-im/feature/bma/fix_new_lint_warning

Fix lint warning
This commit is contained in:
Benoit Marty 2022-09-22 17:35:10 +02:00 committed by GitHub
commit 60bfd0dd42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 545 additions and 505 deletions

1
changelog.d/7159.misc Normal file
View File

@ -0,0 +1 @@
Fix lint warning, and cleanup the code

View File

@ -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" />
</menu>

View File

@ -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)

View File

@ -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,9 +28,13 @@ 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 <T> whenAtLeast(version: Int, result: () -> T): T? {
return if (get() >= version) {
result()
} else null
}
@ChecksSdkIntAtLeast(parameter = 0)
fun isAtLeast(version: Int) = get() >= version
}

View File

@ -19,6 +19,9 @@
<issue id="IconExpectedSize" severity="error" />
<issue id="LocaleFolder" severity="error" />
<!-- AlwaysShowAction is considered as an error to force ignoring the issue when detected -->
<issue id="AlwaysShowAction" severity="error" />
<issue id="TooManyViews" severity="warning">
<!-- Ignore TooManyViews in debug build type -->
<ignore path="**/src/debug/**" />
@ -77,6 +80,7 @@
<issue id="KotlinPropertyAccess" severity="error" />
<issue id="DefaultLocale" severity="error" />
<issue id="CheckResult" severity="error" />
<issue id="StaticFieldLeak" severity="error" />
<issue id="InvalidPackage">
<!-- Ignore error from HtmlCompressor lib -->
@ -105,6 +109,9 @@
<issue id="TypographyDashes" severity="error" />
<issue id="PluralsCandidate" severity="error" />
<!-- Notification -->
<issue id="LaunchActivityFromNotification" severity="error" />
<!-- DI -->
<issue id="JvmStaticProvidesInObjectDetector" severity="error" />
</lint>

View File

@ -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")
}
}

View File

@ -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? {

View File

@ -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
@ -109,6 +109,8 @@ class VectorApplication :
@Inject lateinit var fcmHelper: FcmHelper
@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
@ -159,7 +161,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()
@ -171,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 {

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
)
@ -125,26 +122,18 @@ class LegacyPinCodeMigratorTests {
@Test
fun migratePinCodeM() = runTest {
val pinCode = "1234"
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
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
migratePinCode()
}
@Test
fun migratePinCodeL() = runTest {
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
migratePinCode()
}
private suspend fun migratePinCode() {
val pinCode = "1234"
every { buildVersionSdkIntProvider.get() } returns Build.VERSION_CODES.LOLLIPOP
saveLegacyPinCode(pinCode)
legacyPinCodeMigrator.migrate()
@ -163,7 +152,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 +195,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 +208,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)
}

View File

@ -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)
}
}

View File

@ -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<ConnectivityManager>()!!
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

View File

@ -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.VectorLocaleProvider
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<VB : ViewBinding> : AppCompatActivity(), Maver
@Inject lateinit var rageShake: RageShake
@Inject lateinit var buildMeta: BuildMeta
@Inject lateinit var fontScalePreferences: FontScalePreferences
@Inject lateinit var vectorLocale: VectorLocaleProvider
// For debug only
@Inject lateinit var debugReceiver: DebugReceiver
@ -176,8 +178,10 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private val restorables = ArrayList<Restorable>()
override fun attachBaseContext(base: Context) {
val fontScalePreferences = FontScalePreferencesImpl(PreferenceManager.getDefaultSharedPreferences(base), AndroidSystemSettingsProvider(base))
val vectorConfiguration = VectorConfiguration(this, fontScalePreferences)
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))
}

View File

@ -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.
*

View File

@ -20,11 +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.core.di.DefaultSharedPreferences
import im.vector.app.databinding.ViewKeysBackupBannerBinding
import im.vector.app.features.workers.signout.BannerState
import timber.log.Timber
/**
@ -38,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, "")
}
}
/**
@ -56,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
@ -67,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)
}
@ -133,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 {
@ -148,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
@ -161,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
@ -191,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)
}
}
}
}

View File

@ -17,19 +17,25 @@
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.
*
@ -39,8 +45,8 @@ import im.vector.app.features.settings.VectorPreferences
* @return the [Uri] of the currently set [Ringtone]
* @see Ringtone
*/
fun getCallRingtoneUri(context: Context): Uri? {
val callRingtone: String? = DefaultSharedPreferences.getInstance(context)
fun getCallRingtoneUri(): Uri? {
val callRingtone: String? = sharedPreferences
.getString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, null)
callRingtone?.let {
@ -65,8 +71,8 @@ fun getCallRingtoneUri(context: Context): Uri? {
* @return the currently set [Ringtone]
* @see Ringtone
*/
fun getCallRingtone(context: Context): Ringtone? {
getCallRingtoneUri(context)?.let {
fun getCallRingtone(): Ringtone? {
getCallRingtoneUri()?.let {
// Note that it can also return null
return RingtoneManager.getRingtone(context, it)
}
@ -83,19 +89,18 @@ fun getCallRingtone(context: Context): Ringtone? {
* @return the name of the currently set [Ringtone], or null
* @see Ringtone
*/
fun getCallRingtoneName(context: Context): String? {
return getCallRingtone(context)?.getTitle(context)
fun getCallRingtoneName(): String? {
return getCallRingtone()?.getTitle(context)
}
/**
* Sets the selected ringtone for riot calls.
*
* @param context Android context
* @param ringtoneUri
* @see Ringtone
*/
fun setCallRingtoneUri(context: Context, ringtoneUri: Uri) {
DefaultSharedPreferences.getInstance(context)
fun setCallRingtoneUri(ringtoneUri: Uri) {
sharedPreferences
.edit {
putString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, ringtoneUri.toString())
}
@ -104,16 +109,17 @@ fun setCallRingtoneUri(context: Context, ringtoneUri: Uri) {
/**
* Set using Riot default ringtone.
*/
fun useRiotDefaultRingtone(context: Context): Boolean {
return DefaultSharedPreferences.getInstance(context).getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true)
fun useRiotDefaultRingtone(): Boolean {
return sharedPreferences.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)
fun setUseRiotDefaultRingtone(useRiotDefault: Boolean) {
sharedPreferences
.edit {
putBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, useRiotDefault)
}
}
}

View File

@ -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,6 +49,7 @@ class JitsiService @Inject constructor(
private val themeProvider: ThemeProvider,
private val jitsiJWTFactory: JitsiJWTFactory,
private val clock: Clock,
private val vectorLocale: VectorLocaleProvider,
) {
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)
}
}

View File

@ -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 im.vector.app.features.settings.VectorLocaleProvider
import javax.inject.Inject
@AndroidEntryPoint
class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCallDialPadBinding>() {
companion object {
@ -41,6 +44,8 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCa
}
}
@Inject lateinit var vectorLocale: VectorLocaleProvider
override val showExpanded = true
var callback: DialPadFragment.Callback? = null
@ -62,7 +67,7 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCa
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, showActions)
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, showActions)
putBoolean(DialPadFragment.EXTRA_CURSOR_VISIBLE, false)
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
putString(DialPadFragment.EXTRA_REGION_CODE, vectorLocale.applicationLocale.country)
}
callback = DialPadFragmentCallbackWrapper(this@CallDialPadBottomSheet.callback)
}.also {

View File

@ -28,7 +28,6 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper
import im.vector.app.features.settings.VectorLocale
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
@ -78,7 +77,7 @@ class PstnDialActivity : SimpleFragmentActivity() {
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)
}
callback = object : DialPadFragment.Callback {
override fun onOkClicked(formatted: String?, raw: String?) {

View File

@ -59,7 +59,7 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>() {
}
}
sectionsPagerAdapter = CallTransferPagerAdapter(this)
sectionsPagerAdapter = CallTransferPagerAdapter(this, vectorLocale)
views.callTransferViewPager.adapter = sectionsPagerAdapter
TabLayoutMediator(views.callTransferTabLayout, views.callTransferViewPager) { tab, position ->

View File

@ -22,12 +22,13 @@ import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorLocaleProvider
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: VectorLocaleProvider,
) : 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)
}
}
}

View File

@ -22,7 +22,7 @@ import android.os.Build
import android.os.LocaleList
import androidx.annotation.RequiresApi
import im.vector.app.features.settings.FontScalePreferences
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorLocaleProvider
import im.vector.app.features.themes.ThemeUtils
import timber.log.Timber
import java.util.Locale
@ -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: VectorLocaleProvider,
) {
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))
}

View File

@ -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)
}

View File

@ -29,9 +29,10 @@ import im.vector.app.R
import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorLocaleProvider
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: VectorLocaleProvider
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()
}

View File

@ -28,12 +28,13 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorLocaleProvider
import im.vector.lib.core.utils.flow.throttleFirst
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: VectorLocaleProvider
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
}

View File

@ -17,22 +17,25 @@
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"
class DisclaimerDialog @Inject constructor(
@DefaultPreferences
private val sharedPrefs: SharedPreferences,
) {
fun showDisclaimerDialog(activity: Activity) {
val sharedPrefs = DefaultSharedPreferences.getInstance(activity)
if (sharedPrefs.getInt(SHARED_PREF_KEY, 0) < CURRENT_DISCLAIMER_VALUE) {
sharedPrefs.edit {
putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE)
@ -51,10 +54,9 @@ fun showDisclaimerDialog(activity: Activity) {
}
}
fun doNotShowDisclaimerDialog(context: Context) {
val sharedPrefs = DefaultSharedPreferences.getInstance(context)
fun doNotShowDisclaimerDialog() {
sharedPrefs.edit {
putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE)
}
}
}

View File

@ -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

View File

@ -51,11 +51,12 @@ import im.vector.app.features.home.room.list.RoomListParams
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
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.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
@ -75,6 +76,7 @@ class HomeDetailFragment :
@Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var spaceStateHandler: SpaceStateHandler
@Inject lateinit var vectorLocale: VectorLocaleProvider
private val viewModel: HomeDetailViewModel by fragmentViewModel()
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
@ -288,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
@ -378,7 +382,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()
}
@ -401,6 +405,10 @@ class HomeDetailFragment :
* KeysBackupBanner Listener
* ========================================================================================== */
override fun onCloseClicked() {
serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed)
}
override fun setupKeysBackup() {
navigator.openKeysBackupSetup(requireActivity(), false)
}

View File

@ -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)
}

View File

@ -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

View File

@ -1124,6 +1124,7 @@ class TimelineFragment :
.findViewById<ImageView>(R.id.action_view_icon_image)
.setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary))
actionView.findViewById<TextView>(R.id.cart_badge).setTextOrHide("$widgetsCount")
@Suppress("AlwaysShowAction")
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
}

View File

@ -16,16 +16,22 @@
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"
@ -33,12 +39,13 @@ object ServerUrlsRepository {
// 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 +60,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 +71,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 +84,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(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)
}

View File

@ -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

View File

@ -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)

View File

@ -19,7 +19,6 @@
package im.vector.app.features.notifications
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
@ -34,6 +33,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 +102,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 +127,6 @@ class NotificationUtils @Inject constructor(
/**
* Create notification channels.
*/
@TargetApi(Build.VERSION_CODES.O)
fun createNotificationChannels() {
if (!supportNotificationChannels()) {
return
@ -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
@ -966,6 +963,7 @@ class NotificationUtils @Inject constructor(
}
}
@SuppressLint("LaunchActivityFromNotification")
fun displayDiagnosticNotification() {
val testActionIntent = Intent(context, TestNotificationReceiver::class.java)
testActionIntent.action = actionIds.diagnostic

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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
@ -36,14 +35,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()
}
}

View File

@ -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()

View File

@ -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,7 +167,6 @@ 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()
@ -191,9 +189,8 @@ 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
if (versionProvider.isAtLeast(Build.VERSION_CODES.S)) {
withTimeoutOrNull(5.seconds) {
while (keyguardManager.isDeviceLocked) {
delay(50.milliseconds)
@ -201,3 +198,4 @@ class LockScreenViewModel @AssistedInject constructor(
}
}
}
}

View File

@ -31,7 +31,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.getAllChildFragments
import im.vector.app.core.extensions.toOnOff
import im.vector.app.core.resources.BuildMeta
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.settings.devtools.GossipingEventsSerializer
import im.vector.app.features.settings.locale.SystemLocaleProvider
@ -80,6 +80,7 @@ class BugReporter @Inject constructor(
private val buildMeta: BuildMeta,
private val processInfo: ProcessInfo,
private val sdkIntProvider: BuildVersionSdkIntProvider,
private val vectorLocale: VectorLocaleProvider,
) {
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)

View File

@ -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.
*/

View File

@ -17,30 +17,40 @@
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
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"
@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"
const val APPLICATION_LOCALE_VARIANT_KEY = "APPLICATION_LOCALE_VARIANT_KEY"
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,17 +62,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
val preferences = DefaultSharedPreferences.getInstance(context)
fun init() {
if (preferences.contains(APPLICATION_LOCALE_LANGUAGE_KEY)) {
applicationLocale = Locale(
preferences.getString(APPLICATION_LOCALE_LANGUAGE_KEY, "")!!,
@ -88,7 +91,7 @@ object VectorLocale {
fun saveApplicationLocale(locale: Locale) {
applicationLocale = locale
DefaultSharedPreferences.getInstance(context).edit {
preferences.edit {
val language = locale.language
if (language.isEmpty()) {
remove(APPLICATION_LOCALE_LANGUAGE_KEY)

View File

@ -0,0 +1,41 @@
/*
* 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.settings
import android.content.SharedPreferences
import im.vector.app.core.di.DefaultPreferences
import java.util.Locale
import javax.inject.Inject
/**
* Class to provide the Locale choice of the user.
*/
class VectorLocaleProvider @Inject constructor(
@DefaultPreferences
private val preferences: SharedPreferences,
) {
/**
* Get the current local.
* SharedPref values has been initialized in [VectorLocale.init]
*/
val applicationLocale: Locale
get() = Locale(
preferences.getString(VectorLocale.APPLICATION_LOCALE_LANGUAGE_KEY, "")!!,
preferences.getString(VectorLocale.APPLICATION_LOCALE_COUNTRY_KEY, "")!!,
preferences.getString(VectorLocale.APPLICATION_LOCALE_VARIANT_KEY, "")!!
)
}

View File

@ -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 -> "?"
}
}

View File

@ -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)

View File

@ -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(),

View File

@ -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)
}

View File

@ -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<LocalePickerViewState>() {
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) }
}

View File

@ -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<LocalePickerViewState, LocalePickerAction, LocalePickerViewEvents>(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)
}

View File

@ -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<List<Locale>> = Uninitialized
) : MavericksState

View File

@ -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

View File

@ -20,25 +20,31 @@ 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()) {
VoiceRecorderL(context, Dispatchers.IO)
} else {
return if (useNativeRecorder()) {
VoiceRecorderQ(context)
} else {
VoiceRecorderL(context, Dispatchers.IO)
}
}
private fun useFallbackRecorder(): Boolean {
return Build.VERSION.SDK_INT < 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)

View File

@ -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,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 New Vector Ltd
* 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.
@ -14,18 +14,12 @@
* limitations under the License.
*/
package im.vector.app.core.di
package im.vector.app.features.workers.signout
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import im.vector.app.core.platform.VectorViewModelAction
object DefaultSharedPreferences {
@Volatile private var INSTANCE: SharedPreferences? = null
fun getInstance(context: Context): SharedPreferences =
INSTANCE ?: synchronized(this) {
INSTANCE ?: PreferenceManager.getDefaultSharedPreferences(context.applicationContext).also { INSTANCE = it }
}
sealed interface ServerBackupStatusAction : VectorViewModelAction {
data class OnRecoverDoneForVersion(val version: String) : ServerBackupStatusAction
object OnBannerDisplayed : ServerBackupStatusAction
object OnBannerClosed : ServerBackupStatusAction
}

View File

@ -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<ServerBackupStatusViewState, EmptyAction, EmptyViewEvents>(initialState), KeysBackupStateListener {
VectorViewModel<ServerBackupStatusViewState, ServerBackupStatusAction, EmptyViewEvents>(initialState), KeysBackupStateListener {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<ServerBackupStatusViewModel, ServerBackupStatusViewState> {
override fun create(initialState: ServerBackupStatusViewState): ServerBackupStatusViewModel
}
companion object : MavericksViewModelFactory<ServerBackupStatusViewModel, ServerBackupStatusViewState> by hiltMavericksViewModelFactory()
companion object : MavericksViewModelFactory<ServerBackupStatusViewModel, ServerBackupStatusViewState> 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<Boolean>()
@ -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
}
}
}
}

View File

@ -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" />
</menu>

View File

@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete_unsent_messages"
android:title="@string/action_delete"
app:showAsAction="always" />
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/action_mark_as_suggested"