diff --git a/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt index 4ca6ced8fe..573138bf5c 100644 --- a/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt @@ -17,6 +17,7 @@ package im.vector.lib.attachmentviewer +import android.annotation.SuppressLint import android.graphics.Color import android.os.Build import android.os.Bundle @@ -141,7 +142,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_IMMERSIVE - window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } else { + @SuppressLint("WrongConstant") + window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + } // New API instead of FLAG_TRANSLUCENT_STATUS window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // new API instead of FLAG_TRANSLUCENT_NAVIGATION @@ -347,7 +353,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi // new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) // New API instead of SYSTEM_UI_FLAG_IMMERSIVE - window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } else { + @SuppressLint("WrongConstant") + window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + } // New API instead of FLAG_TRANSLUCENT_STATUS window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_NAVIGATION diff --git a/dependencies.gradle b/dependencies.gradle index db1e6523c3..85f00f95ea 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,8 +1,8 @@ ext.versions = [ 'minSdk' : 21, - 'compileSdk' : 30, - 'targetSdk' : 30, + 'compileSdk' : 31, + 'targetSdk' : 31, 'sourceCompat' : JavaVersion.VERSION_11, 'targetCompat' : JavaVersion.VERSION_11, ] @@ -16,7 +16,7 @@ def retrofit = "2.9.0" def arrow = "0.8.2" def markwon = "4.6.2" def moshi = "1.12.0" -def lifecycle = "2.2.0" +def lifecycle = "2.4.0" def flowBinding = "1.2.0" def epoxy = "4.6.2" def mavericks = "2.4.0" @@ -46,18 +46,18 @@ ext.libs = [ ], androidx : [ 'appCompat' : "androidx.appcompat:appcompat:1.3.1", - 'core' : "androidx.core:core-ktx:1.6.0", + 'core' : "androidx.core:core-ktx:1.7.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3", 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6", 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.1", - 'work' : "androidx.work:work-runtime-ktx:2.6.0", + 'work' : "androidx.work:work-runtime-ktx:2.7.0", 'autoFill' : "androidx.autofill:autofill:1.1.0", 'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1", 'junit' : "androidx.test.ext:junit:1.1.3", - 'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle", - 'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle", - 'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1", + 'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle", + 'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle", + 'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle", 'datastore' : "androidx.datastore:datastore:1.0.0", 'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0", 'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2", diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 67e5eee74b..94fc221cf1 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -44,6 +44,7 @@ android { } testOptions { + // Comment to run on Android 12 execution 'ANDROIDX_TEST_ORCHESTRATOR' } @@ -106,8 +107,9 @@ dependencies { implementation libs.androidx.appCompat implementation libs.androidx.core - implementation libs.androidx.lifecycleExtensions - implementation libs.androidx.lifecycleJava8 + // Lifecycle + implementation libs.androidx.lifecycleCommon + implementation libs.androidx.lifecycleProcess // Network implementation libs.squareup.retrofit diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt index a12587ac56..3e977b31fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt @@ -16,9 +16,8 @@ package org.matrix.android.sdk.internal.util -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import org.matrix.android.sdk.internal.di.MatrixScope import timber.log.Timber import javax.inject.Inject @@ -27,13 +26,12 @@ import javax.inject.Inject * To be attached to ProcessLifecycleOwner lifecycle */ @MatrixScope -internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObserver { +internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecycleObserver { var isInBackground: Boolean = true private set - private - val listeners = LinkedHashSet() + private val listeners = LinkedHashSet() fun register(listener: Listener) { listeners.add(listener) @@ -43,15 +41,13 @@ internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObse listeners.remove(listener) } - @OnLifecycleEvent(Lifecycle.Event.ON_START) - fun onMoveToForeground() { + override fun onStart(owner: LifecycleOwner) { Timber.v("App returning to foreground…") isInBackground = false listeners.forEach { it.onMoveToForeground() } } - @OnLifecycleEvent(Lifecycle.Event.ON_STOP) - fun onMoveToBackground() { + override fun onStop(owner: LifecycleOwner) { Timber.v("App going to background…") isInBackground = true listeners.forEach { it.onMoveToBackground() } diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt index 315fe6cbf2..2b8c1d11e6 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.provider.ContactsContract import im.vector.lib.multipicker.entity.MultiPickerContactType +import im.vector.lib.multipicker.utils.getColumnIndexOrNull /** * Contact Picker implementation @@ -49,9 +50,9 @@ class ContactPicker : Picker() { null )?.use { cursor -> if (cursor.moveToFirst()) { - val idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID) - val nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) - val photoUriColumn = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI) + val idColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts._ID) ?: return@use + val nameColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.DISPLAY_NAME) ?: return@use + val photoUriColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.PHOTO_URI) ?: return@use val contactId = cursor.getInt(idColumn) var name = cursor.getString(nameColumn) @@ -72,10 +73,13 @@ class ContactPicker : Picker() { selection, selectionArgs, null - )?.use { cursor -> - while (cursor.moveToNext()) { - val mimeType = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)) - val contactData = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1)) + )?.use inner@{ innerCursor -> + val mimeTypeColumnIndex = innerCursor.getColumnIndexOrNull(ContactsContract.Data.MIMETYPE) ?: return@inner + val data1ColumnIndex = innerCursor.getColumnIndexOrNull(ContactsContract.Data.DATA1) ?: return@inner + + while (innerCursor.moveToNext()) { + val mimeType = innerCursor.getString(mimeTypeColumnIndex) + val contactData = innerCursor.getString(data1ColumnIndex) if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) { name = contactData @@ -115,7 +119,10 @@ class ContactPicker : Picker() { selectionArgs, null )?.use { cursor -> - return if (cursor.moveToFirst()) cursor.getInt(cursor.getColumnIndex(ContactsContract.RawContacts._ID)) else null + return if (cursor.moveToFirst()) { + cursor.getColumnIndexOrNull(ContactsContract.RawContacts._ID) + ?.let { cursor.getInt(it) } + } else null } } diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt index ec98152aa7..8e6c97f2f8 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.provider.OpenableColumns import im.vector.lib.multipicker.entity.MultiPickerBaseType import im.vector.lib.multipicker.entity.MultiPickerFileType +import im.vector.lib.multipicker.utils.getColumnIndexOrNull import im.vector.lib.multipicker.utils.isMimeTypeAudio import im.vector.lib.multipicker.utils.isMimeTypeImage import im.vector.lib.multipicker.utils.isMimeTypeVideo @@ -49,8 +50,8 @@ class FilePicker : Picker() { // Other files context.contentResolver.query(selectedUri, null, null, null, null) ?.use { cursor -> - val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) - val sizeColumn = cursor.getColumnIndex(OpenableColumns.SIZE) + val nameColumn = cursor.getColumnIndexOrNull(OpenableColumns.DISPLAY_NAME) ?: return@use null + val sizeColumn = cursor.getColumnIndexOrNull(OpenableColumns.SIZE) ?: return@use null if (cursor.moveToFirst()) { val name = cursor.getString(nameColumn) val size = cursor.getLong(sizeColumn) diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt index 78136c274a..55c0010afd 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt @@ -37,8 +37,8 @@ internal fun Uri.toMultiPickerImageType(context: Context): MultiPickerImageType? null, null )?.use { cursor -> - val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) - val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE) + val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Images.Media.DISPLAY_NAME) ?: return@use null + val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Images.Media.SIZE) ?: return@use null if (cursor.moveToNext()) { val name = cursor.getString(nameColumn) @@ -75,8 +75,8 @@ internal fun Uri.toMultiPickerVideoType(context: Context): MultiPickerVideoType? null, null )?.use { cursor -> - val nameColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME) - val sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE) + val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Video.Media.DISPLAY_NAME) ?: return@use null + val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Video.Media.SIZE) ?: return@use null if (cursor.moveToNext()) { val name = cursor.getString(nameColumn) @@ -124,8 +124,8 @@ fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? { null, null )?.use { cursor -> - val nameColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME) - val sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE) + val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Audio.Media.DISPLAY_NAME) ?: return@use null + val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Audio.Media.SIZE) ?: return@use null if (cursor.moveToNext()) { val name = cursor.getString(nameColumn) diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/utils/CursorExtensions.kt b/multipicker/src/main/java/im/vector/lib/multipicker/utils/CursorExtensions.kt new file mode 100644 index 0000000000..87cf48d0a7 --- /dev/null +++ b/multipicker/src/main/java/im/vector/lib/multipicker/utils/CursorExtensions.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.lib.multipicker.utils + +import android.database.Cursor + +fun Cursor.getColumnIndexOrNull(column: String): Int? { + return getColumnIndex(column).takeIf { it != -1 } +} diff --git a/tools/release/sign_apk.sh b/tools/release/sign_apk.sh index 7697f58ceb..aae9e1a378 100755 --- a/tools/release/sign_apk.sh +++ b/tools/release/sign_apk.sh @@ -17,7 +17,7 @@ PARAM_KEYSTORE_PATH=$1 PARAM_APK=$2 # Other params -BUILD_TOOLS_VERSION="30.0.3" +BUILD_TOOLS_VERSION="31.0.0-rc5" MIN_SDK_VERSION=21 echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..." diff --git a/tools/release/sign_apk_unsafe.sh b/tools/release/sign_apk_unsafe.sh index af5b0f0e32..5d209a4a2b 100755 --- a/tools/release/sign_apk_unsafe.sh +++ b/tools/release/sign_apk_unsafe.sh @@ -23,7 +23,7 @@ PARAM_KS_PASS=$3 PARAM_KEY_PASS=$4 # Other params -BUILD_TOOLS_VERSION="30.0.3" +BUILD_TOOLS_VERSION="31.0.0-rc5" MIN_SDK_VERSION=21 echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..." diff --git a/vector/build.gradle b/vector/build.gradle index 298136129a..655cff2e73 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -210,6 +210,7 @@ android { // This property does not affect tests that you run using Android Studio.” animationsDisabled = true + // Comment to run on Android 12 execution 'ANDROIDX_TEST_ORCHESTRATOR' } @@ -356,8 +357,10 @@ dependencies { implementation libs.squareup.moshi kapt libs.squareup.moshiKotlin - implementation libs.androidx.lifecycleExtensions + + // Lifecycle implementation libs.androidx.lifecycleLivedata + implementation libs.androidx.lifecycleProcess implementation libs.androidx.datastore implementation libs.androidx.datastorepreferences @@ -411,7 +414,7 @@ dependencies { implementation 'com.github.Armen101:AudioRecordView:1.0.5' // Custom Tab - implementation 'androidx.browser:browser:1.3.0' + implementation 'androidx.browser:browser:1.4.0' // Passphrase strength helper implementation 'com.nulab-inc:zxcvbn:1.5.2' diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt index bb1cb622c0..2e329ebb6b 100644 --- a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt @@ -20,6 +20,7 @@ import android.content.ContentResolver import android.content.ContentValues import android.graphics.Bitmap import android.net.Uri +import android.os.Build import android.os.Environment import android.provider.MediaStore import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation @@ -55,7 +56,7 @@ private fun storeFailureScreenshot(bitmap: Bitmap, screenshotName: String) { put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) } - if (android.os.Build.VERSION.SDK_INT >= 29) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { useMediaStoreScreenshotStorage( contentValues, contentResolver, @@ -90,6 +91,7 @@ private fun useMediaStoreScreenshotStorage( } } +@Suppress("DEPRECATION") private fun usePublicExternalScreenshotStorage( contentValues: ContentValues, contentResolver: ContentResolver, diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 022b08f16d..ea9fa023ab 100644 --- a/vector/src/fdroid/AndroidManifest.xml +++ b/vector/src/fdroid/AndroidManifest.xml @@ -14,7 +14,9 @@ - + diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 0f375561b2..c1fda2d404 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -25,6 +25,7 @@ import android.os.Build import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import im.vector.app.core.extensions.singletonEntryPoint +import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.core.services.VectorSyncService import org.matrix.android.sdk.internal.session.sync.job.SyncService import timber.log.Timber @@ -67,7 +68,12 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { putExtra(SyncService.EXTRA_SESSION_ID, sessionId) putExtra(SyncService.EXTRA_PERIODIC, true) } - val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) + val pIntent = PendingIntent.getBroadcast( + context, + REQUEST_CODE, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) val firstMillis = System.currentTimeMillis() + delayInSeconds * 1000L val alarmMgr = context.getSystemService()!! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -80,7 +86,12 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { fun cancelAlarm(context: Context) { Timber.v("## Sync: Cancel alarm for background sync") val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) - val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) + val pIntent = PendingIntent.getBroadcast( + context, + REQUEST_CODE, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) val alarmMgr = context.getSystemService()!! alarmMgr.cancel(pIntent) diff --git a/vector/src/gplay/AndroidManifest.xml b/vector/src/gplay/AndroidManifest.xml index d849d5fb2d..f541eebd83 100755 --- a/vector/src/gplay/AndroidManifest.xml +++ b/vector/src/gplay/AndroidManifest.xml @@ -9,7 +9,9 @@ android:name="firebase_analytics_collection_deactivated" android:value="true" /> - + diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 01d7630678..e5d4753235 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -4,7 +4,10 @@ package="im.vector.app"> - + + @@ -418,6 +421,22 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/sdk_provider_paths" /> + + + + + + diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index b6d41ce35d..9ed9dd5b23 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -16,9 +16,8 @@ package im.vector.app -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import arrow.core.Option import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource @@ -57,7 +56,7 @@ class AppStateHandler @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, private val uiStateRepository: UiStateRepository, private val activeSessionHolder: ActiveSessionHolder -) : LifecycleObserver { +) : DefaultLifecycleObserver { private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val selectedSpaceDataSource = BehaviorDataSource>(Option.empty()) @@ -133,13 +132,11 @@ class AppStateHandler @Inject constructor( return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId } - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - fun entersForeground() { + override fun onResume(owner: LifecycleOwner) { observeActiveSession() } - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) - fun entersBackground() { + override fun onPause(owner: LifecycleOwner) { coroutineScope.coroutineContext.cancelChildren() val session = activeSessionHolder.getSafeActiveSession() ?: return when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 80b397231b..c1d9eef125 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -27,9 +27,8 @@ import android.os.HandlerThread import android.os.StrictMode import androidx.core.provider.FontRequest import androidx.core.provider.FontsContractCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDex import com.airbnb.epoxy.EpoxyAsyncUtil @@ -166,9 +165,8 @@ class VectorApplication : ProcessLifecycleOwner.get().lifecycle.addObserver(startSyncOnFirstStart) - ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver { - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - fun entersForeground() { + ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { Timber.i("App entered foreground") FcmHelper.onEnterForeground(appContext, activeSessionHolder) activeSessionHolder.getSafeActiveSession()?.also { @@ -176,8 +174,7 @@ class VectorApplication : } } - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) - fun entersBackground() { + override fun onPause(owner: LifecycleOwner) { Timber.i("App entered background") // call persistInfo notificationDrawerManager.persistInfo() FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder) @@ -198,9 +195,8 @@ class VectorApplication : EmojiManager.install(GoogleEmojiProvider()) } - private val startSyncOnFirstStart = object : LifecycleObserver { - @OnLifecycleEvent(Lifecycle.Event.ON_START) - fun onStart() { + private val startSyncOnFirstStart = object : DefaultLifecycleObserver { + override fun onStart(owner: LifecycleOwner) { Timber.i("App process started") authenticationService.getLastAuthenticatedSession()?.startSyncing(appContext) ProcessLifecycleOwner.get().lifecycle.removeObserver(this) diff --git a/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt index f5a5e240c6..f99048501d 100644 --- a/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt +++ b/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt @@ -17,10 +17,10 @@ package im.vector.app.core.contacts import android.content.Context -import android.database.Cursor import android.net.Uri import android.provider.ContactsContract import androidx.annotation.WorkerThread +import im.vector.lib.multipicker.utils.getColumnIndexOrNull import timber.log.Timber import javax.inject.Inject import kotlin.system.measureTimeMillis @@ -57,16 +57,20 @@ class ContactsDataSource @Inject constructor( ) ?.use { cursor -> if (cursor.count > 0) { + val idColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.Contacts._ID) ?: return@use + val displayNameColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.Contacts.DISPLAY_NAME) ?: return@use + val photoUriColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.Data.PHOTO_URI) while (cursor.moveToNext()) { - val id = cursor.getLong(ContactsContract.Contacts._ID) ?: continue - val displayName = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) ?: continue + val id = cursor.getLong(idColumnIndex) + val displayName = cursor.getString(displayNameColumnIndex) val mappedContactBuilder = MappedContactBuilder( id = id, displayName = displayName ) - cursor.getString(ContactsContract.Data.PHOTO_URI) + photoUriColumnIndex + ?.let { cursor.getString(it) } ?.let { Uri.parse(it) } ?.let { mappedContactBuilder.photoURI = it } @@ -85,12 +89,15 @@ class ContactsDataSource @Inject constructor( null, null, null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { - val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) - ?.let { map[it] } + ?.use { cursor -> + val idColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) ?: return@use + val phoneNumberColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Phone.NUMBER) ?: return@use + + while (cursor.moveToNext()) { + val mappedContactBuilder = cursor.getLong(idColumnIndex) + .let { map[it] } ?: continue - innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) + cursor.getString(phoneNumberColumnIndex) ?.let { mappedContactBuilder.msisdns.add( MappedMsisdn( @@ -114,14 +121,17 @@ class ContactsDataSource @Inject constructor( null, null, null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { + ?.use { cursor -> + val idColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Email.CONTACT_ID) ?: return@use + val emailColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Email.DATA) ?: return@use + + while (cursor.moveToNext()) { // This would allow you get several email addresses // if the email addresses were stored in an array - val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Email.CONTACT_ID) - ?.let { map[it] } + val mappedContactBuilder = cursor.getLong(idColumnIndex) + .let { map[it] } ?: continue - innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) + cursor.getString(emailColumnIndex) ?.let { mappedContactBuilder.emails.add( MappedEmail( @@ -140,16 +150,4 @@ class ContactsDataSource @Inject constructor( .filter { it.emails.isNotEmpty() || it.msisdns.isNotEmpty() } .map { it.build() } } - - private fun Cursor.getString(column: String): String? { - return getColumnIndex(column) - .takeIf { it != -1 } - ?.let { getString(it) } - } - - private fun Cursor.getLong(column: String): Long? { - return getColumnIndex(column) - .takeIf { it != -1 } - ?.let { getLong(it) } - } } diff --git a/vector/src/main/java/im/vector/app/core/intent/Filename.kt b/vector/src/main/java/im/vector/app/core/intent/Filename.kt index 5d118c19a1..a38602e4a5 100644 --- a/vector/src/main/java/im/vector/app/core/intent/Filename.kt +++ b/vector/src/main/java/im/vector/app/core/intent/Filename.kt @@ -19,15 +19,17 @@ package im.vector.app.core.intent import android.content.Context import android.net.Uri import android.provider.OpenableColumns +import im.vector.lib.multipicker.utils.getColumnIndexOrNull fun getFilenameFromUri(context: Context?, uri: Uri): String? { if (context != null && uri.scheme == "content") { - val cursor = context.contentResolver.query(uri, null, null, null, null) - cursor?.use { - if (it.moveToFirst()) { - return it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) - } - } + context.contentResolver.query(uri, null, null, null, null) + ?.use { cursor -> + if (cursor.moveToFirst()) { + return cursor.getColumnIndexOrNull(OpenableColumns.DISPLAY_NAME) + ?.let { cursor.getString(it) } + } + } } return uri.path?.substringAfterLast('/') } diff --git a/vector/src/main/java/im/vector/app/core/platform/LifecycleAwareLazy.kt b/vector/src/main/java/im/vector/app/core/platform/LifecycleAwareLazy.kt index 283106232e..54add00459 100644 --- a/vector/src/main/java/im/vector/app/core/platform/LifecycleAwareLazy.kt +++ b/vector/src/main/java/im/vector/app/core/platform/LifecycleAwareLazy.kt @@ -18,58 +18,56 @@ package im.vector.app.core.platform import androidx.annotation.MainThread import androidx.fragment.app.Fragment +import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.OnLifecycleEvent fun LifecycleOwner.lifecycleAwareLazy(initializer: () -> T): Lazy = LifecycleAwareLazy(this, initializer) private object UninitializedValue class LifecycleAwareLazy( - private val owner: LifecycleOwner, - initializer: () -> T -) : Lazy, LifecycleObserver { + private val owner: LifecycleOwner, + initializer: () -> T +) : Lazy, DefaultLifecycleObserver { - private var initializer: (() -> T)? = initializer + private var initializer: (() -> T)? = initializer - private var _value: Any? = UninitializedValue + private var _value: Any? = UninitializedValue - @Suppress("UNCHECKED_CAST") - override val value: T - @MainThread - get() { - if (_value === UninitializedValue) { - _value = initializer!!() - attachToLifecycle() - } - return _value as T + @Suppress("UNCHECKED_CAST") + override val value: T + @MainThread + get() { + if (_value === UninitializedValue) { + _value = initializer!!() + attachToLifecycle() + } + return _value as T + } + + override fun onDestroy(owner: LifecycleOwner) { + _value = UninitializedValue + detachFromLifecycle() } - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - fun resetValue() { - _value = UninitializedValue - detachFromLifecycle() - } - - private fun attachToLifecycle() { - if (getLifecycleOwner().lifecycle.currentState == Lifecycle.State.DESTROYED) { - throw IllegalStateException("Initialization failed because lifecycle has been destroyed!") + private fun attachToLifecycle() { + if (getLifecycleOwner().lifecycle.currentState == Lifecycle.State.DESTROYED) { + throw IllegalStateException("Initialization failed because lifecycle has been destroyed!") + } + getLifecycleOwner().lifecycle.addObserver(this) } - getLifecycleOwner().lifecycle.addObserver(this) - } - private fun detachFromLifecycle() { - getLifecycleOwner().lifecycle.removeObserver(this) - } + private fun detachFromLifecycle() { + getLifecycleOwner().lifecycle.removeObserver(this) + } - private fun getLifecycleOwner() = when (owner) { - is Fragment -> owner.viewLifecycleOwner - else -> owner - } + private fun getLifecycleOwner() = when (owner) { + is Fragment -> owner.viewLifecycleOwner + else -> owner + } - override fun isInitialized(): Boolean = _value !== UninitializedValue + override fun isInitialized(): Boolean = _value !== UninitializedValue - override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." + override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." } diff --git a/vector/src/main/java/im/vector/app/features/call/telecom/TelecomUtils.kt b/vector/src/main/java/im/vector/app/core/platform/PendingIntentCompat.kt similarity index 55% rename from vector/src/main/java/im/vector/app/features/call/telecom/TelecomUtils.kt rename to vector/src/main/java/im/vector/app/core/platform/PendingIntentCompat.kt index 819a1f6c0a..832c11888c 100644 --- a/vector/src/main/java/im/vector/app/features/call/telecom/TelecomUtils.kt +++ b/vector/src/main/java/im/vector/app/core/platform/PendingIntentCompat.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,21 @@ * limitations under the License. */ -package im.vector.app.features.call.telecom +package im.vector.app.core.platform -import android.content.Context -import android.telephony.TelephonyManager -import androidx.core.content.getSystemService +import android.app.PendingIntent +import android.os.Build -object TelecomUtils { +object PendingIntentCompat { + val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_IMMUTABLE + } else { + 0 + } - fun isLineBusy(context: Context): Boolean { - val telephonyManager = context.getSystemService() - ?: return false - return telephonyManager.callState != TelephonyManager.CALL_STATE_IDLE + val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE + } else { + 0 } } 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 7fe939bef3..0fd660effb 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 @@ -16,6 +16,7 @@ package im.vector.app.core.platform +import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.res.Configuration @@ -403,7 +404,12 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_IMMERSIVE - window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } else { + @SuppressLint("WrongConstant") + window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + } // New API instead of FLAG_TRANSLUCENT_STATUS window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.attachmentviewer.R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_NAVIGATION diff --git a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt index 08db8227a6..a6f6d8d82f 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt @@ -32,6 +32,7 @@ import androidx.work.Worker import androidx.work.WorkerParameters import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R +import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.settings.BackgroundSyncMode import org.matrix.android.sdk.internal.session.sync.job.SyncService @@ -199,9 +200,9 @@ private fun Context.rescheduleSyncService(sessionId: String, startService(intent) } else { val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(this, 0, intent, 0) + PendingIntent.getForegroundService(this, 0, intent, PendingIntentCompat.FLAG_IMMUTABLE) } else { - PendingIntent.getService(this, 0, intent, 0) + PendingIntent.getService(this, 0, intent, PendingIntentCompat.FLAG_IMMUTABLE) } val firstMillis = System.currentTimeMillis() + syncDelaySeconds * 1000L val alarmMgr = getSystemService()!! diff --git a/vector/src/main/java/im/vector/app/features/call/conference/ConferenceEvent.kt b/vector/src/main/java/im/vector/app/features/call/conference/ConferenceEvent.kt index 0a63ad6907..0d8e538eca 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/ConferenceEvent.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/ConferenceEvent.kt @@ -20,9 +20,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.facebook.react.bridge.JavaOnlyMap import org.jitsi.meet.sdk.BroadcastEmitter @@ -42,7 +41,7 @@ sealed class ConferenceEvent(open val data: Map) { } } -class ConferenceEventEmitter(private val context: Context) { +class ConferenceEventEmitter(private val context: Context) { fun emitConferenceEnded() { val broadcastEventData = JavaOnlyMap.of(CONFERENCE_URL_DATA_KEY, JitsiMeet.getCurrentConference()) @@ -52,7 +51,7 @@ class ConferenceEventEmitter(private val context: Context) { class ConferenceEventObserver(private val context: Context, private val onBroadcastEvent: (ConferenceEvent) -> Unit) : - LifecycleObserver { + DefaultLifecycleObserver { // See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events private val broadcastReceiver = object : BroadcastReceiver() { @@ -61,8 +60,7 @@ class ConferenceEventObserver(private val context: Context, } } - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - fun unregisterForBroadcastMessages() { + override fun onDestroy(owner: LifecycleOwner) { try { LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver) } catch (throwable: Throwable) { @@ -70,8 +68,7 @@ class ConferenceEventObserver(private val context: Context, } } - @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) - fun registerForBroadcastMessages() { + override fun onCreate(owner: LifecycleOwner) { val intentFilter = IntentFilter() for (type in BroadcastEvent.Type.values()) { intentFilter.addAction(type.action) diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index 9e620174f3..80390a7dfb 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -17,9 +17,8 @@ package im.vector.app.features.call.webrtc import android.content.Context -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import im.vector.app.ActiveSessionDataSource import im.vector.app.BuildConfig import im.vector.app.core.services.CallService @@ -70,7 +69,8 @@ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP) class WebRtcCallManager @Inject constructor( private val context: Context, private val activeSessionDataSource: ActiveSessionDataSource -) : CallListener, LifecycleObserver { +) : CallListener, + DefaultLifecycleObserver { private val currentSession: Session? get() = activeSessionDataSource.currentValue?.orNull() @@ -133,13 +133,11 @@ class WebRtcCallManager @Inject constructor( private var isInBackground: Boolean = true - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - fun entersForeground() { + override fun onResume(owner: LifecycleOwner) { isInBackground = false } - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) - fun entersBackground() { + override fun onPause(owner: LifecycleOwner) { isInBackground = true } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt index ddbe86305c..a0a6a138dc 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt @@ -29,13 +29,8 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor( private val stringProvider: StringProvider ) : ViewModel() { - var recoveryCode: MutableLiveData = MutableLiveData() - var recoveryCodeErrorText: MutableLiveData = MutableLiveData() - - init { - recoveryCode.value = null - recoveryCodeErrorText.value = null - } + var recoveryCode: MutableLiveData = MutableLiveData(null) + var recoveryCodeErrorText: MutableLiveData = MutableLiveData(null) // ========= Actions ========= fun updateCode(newValue: String) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt index af5938e20a..81d3c64dec 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt @@ -28,13 +28,8 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor( private val stringProvider: StringProvider ) : ViewModel() { - var passphrase: MutableLiveData = MutableLiveData() - var passphraseErrorText: MutableLiveData = MutableLiveData() - - init { - passphrase.value = null - passphraseErrorText.value = null - } + var passphrase: MutableLiveData = MutableLiveData(null) + var passphraseErrorText: MutableLiveData = MutableLiveData(null) // ========= Actions ========= diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt index 34a333d588..8362a3566e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt @@ -57,7 +57,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( lateinit var session: Session - var keyVersionResult: MutableLiveData = MutableLiveData() + var keyVersionResult: MutableLiveData = MutableLiveData(null) var keySourceModel: MutableLiveData = MutableLiveData() @@ -69,17 +69,11 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( val navigateEvent: LiveData> get() = _navigateEvent - var loadingEvent: MutableLiveData = MutableLiveData() + var loadingEvent: MutableLiveData = MutableLiveData(null) var importKeyResult: ImportRoomKeysResult? = null var importRoomKeysFinishWithResult: MutableLiveData> = MutableLiveData() - init { - keyVersionResult.value = null - _keyVersionResultError.value = null - loadingEvent.value = null - } - fun initSession(session: Session) { this.session = session } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt index cd59a69a86..1141886689 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt @@ -68,23 +68,15 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { // Step 3 // Var to ignore events from previous request(s) to generate a recovery key private var currentRequestId: MutableLiveData = MutableLiveData() - var recoveryKey: MutableLiveData = MutableLiveData() - var prepareRecoverFailError: MutableLiveData = MutableLiveData() + var recoveryKey: MutableLiveData = MutableLiveData(null) + var prepareRecoverFailError: MutableLiveData = MutableLiveData(null) var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null var copyHasBeenMade = false - var isCreatingBackupVersion: MutableLiveData = MutableLiveData() - var creatingBackupError: MutableLiveData = MutableLiveData() + var isCreatingBackupVersion: MutableLiveData = MutableLiveData(false) + var creatingBackupError: MutableLiveData = MutableLiveData(null) var keysVersion: MutableLiveData = MutableLiveData() - var loadingStatus: MutableLiveData = MutableLiveData() - - init { - recoveryKey.value = null - isCreatingBackupVersion.value = false - prepareRecoverFailError.value = null - creatingBackupError.value = null - loadingStatus.value = null - } + var loadingStatus: MutableLiveData = MutableLiveData(null) fun initSession(session: Session) { this.session = session diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt index 232323789c..03107fd3f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt @@ -17,13 +17,15 @@ package im.vector.app.features.home.room.detail.composer +import android.content.ClipData import android.content.Context import android.net.Uri -import android.os.Build import android.text.Editable import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection +import androidx.core.view.OnReceiveContentListener +import androidx.core.view.ViewCompat import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.InputConnectionCompat import com.vanniktech.emoji.EmojiEditText @@ -33,7 +35,7 @@ import im.vector.app.features.html.PillImageSpan import timber.log.Timber class ComposerEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle) : - EmojiEditText(context, attrs, defStyleAttr) { + EmojiEditText(context, attrs, defStyleAttr) { interface Callback { fun onRichContentSelected(contentUri: Uri): Boolean @@ -43,23 +45,35 @@ class ComposerEditText @JvmOverloads constructor(context: Context, attrs: Attrib var callback: Callback? = null override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection? { - val ic = super.onCreateInputConnection(editorInfo) ?: return null - EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("*/*")) + var ic = super.onCreateInputConnection(editorInfo) ?: return null + val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this) ?: arrayOf("image/*") - val callback = - InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, _ -> - val lacksPermission = (flags and - InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) { - try { - inputContentInfo.requestPermission() - } catch (e: Exception) { - return@OnCommitContentListener false - } - } - callback?.onRichContentSelected(inputContentInfo.contentUri) ?: false + EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes) + ic = InputConnectionCompat.createWrapper(this, ic, editorInfo) + + val onReceiveContentListener = OnReceiveContentListener { _, payload -> + val split = payload.partition { item -> item.uri != null } + val uriContent = split.first + val remaining = split.second + + if (uriContent != null) { + val clip: ClipData = uriContent.clip + for (i in 0 until clip.itemCount) { + val uri = clip.getItemAt(i).uri + // ... app-specific logic to handle the URI ... + callback?.onRichContentSelected(uri) } - return InputConnectionCompat.createWrapper(ic, editorInfo, callback) + } + // Return anything that we didn't handle ourselves. This preserves the default platform + // behavior for text and anything else for which we are not implementing custom handling. + // Return anything that we didn't handle ourselves. This preserves the default platform + // behavior for text and anything else for which we are not implementing custom handling. + remaining + } + + ViewCompat.setOnReceiveContentListener(this, mimeTypes, onReceiveContentListener) + + return ic } init { 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 491302a225..1479e577e7 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 @@ -48,6 +48,7 @@ import androidx.fragment.app.Fragment import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.createIgnoredUri +import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.core.resources.StringProvider import im.vector.app.core.services.CallService import im.vector.app.core.utils.startNotificationChannelSettingsIntent @@ -227,7 +228,7 @@ class NotificationUtils @Inject constructor(private val context: Context, // build the pending intent go to the home screen if this is clicked. val i = HomeActivity.newIntent(context) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - val pi = PendingIntent.getActivity(context, 0, i, 0) + val pi = PendingIntent.getActivity(context, 0, i, PendingIntentCompat.FLAG_IMMUTABLE) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) @@ -320,16 +321,23 @@ class NotificationUtils @Inject constructor(private val context: Context, flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP data = createIgnoredUri(call.callId) } - val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) + val contentPendingIntent = PendingIntent.getActivity( + context, + System.currentTimeMillis().toInt(), + contentIntent, + PendingIntentCompat.FLAG_IMMUTABLE + ) val answerCallPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) - .addNextIntent(VectorCallActivity.newIntent( - context = context, - call = call, - mode = VectorCallActivity.INCOMING_ACCEPT) + .addNextIntent( + VectorCallActivity.newIntent( + context = context, + call = call, + mode = VectorCallActivity.INCOMING_ACCEPT + ) ) - .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) + .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId) @@ -338,7 +346,8 @@ class NotificationUtils @Inject constructor(private val context: Context, IconCompat.createWithResource(context, R.drawable.ic_call_hangup) .setTint(ThemeUtils.getColor(context, R.attr.colorError)), getActionText(R.string.call_notification_reject, R.attr.colorError), - rejectCallPendingIntent) + rejectCallPendingIntent + ) ) builder.addAction( @@ -381,7 +390,12 @@ class NotificationUtils @Inject constructor(private val context: Context, flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP data = createIgnoredUri(call.callId) } - val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) + val contentPendingIntent = PendingIntent.getActivity( + context, + System.currentTimeMillis().toInt(), + contentIntent, + PendingIntentCompat.FLAG_IMMUTABLE + ) val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId) @@ -390,7 +404,8 @@ class NotificationUtils @Inject constructor(private val context: Context, IconCompat.createWithResource(context, R.drawable.ic_call_hangup) .setTint(ThemeUtils.getColor(context, R.attr.colorError)), getActionText(R.string.call_notification_hangup, R.attr.colorError), - rejectCallPendingIntent) + rejectCallPendingIntent + ) ) builder.setContentIntent(contentPendingIntent) @@ -431,13 +446,14 @@ class NotificationUtils @Inject constructor(private val context: Context, IconCompat.createWithResource(context, R.drawable.ic_call_hangup) .setTint(ThemeUtils.getColor(context, R.attr.colorError)), getActionText(R.string.call_notification_hangup, R.attr.colorError), - rejectCallPendingIntent) + rejectCallPendingIntent + ) ) val contentPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(VectorCallActivity.newIntent(context, call, null)) - .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) + .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) builder.setContentIntent(contentPendingIntent) @@ -453,7 +469,7 @@ class NotificationUtils @Inject constructor(private val context: Context, context, System.currentTimeMillis().toInt(), rejectCallActionReceiver, - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) } @@ -499,7 +515,7 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(RoomDetailActivity.newIntent(context, RoomDetailArgs(callInformation.nativeRoomId))) - .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) + .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) builder.setContentIntent(contentPendingIntent) return builder.build() @@ -517,7 +533,10 @@ class NotificationUtils @Inject constructor(private val context: Context, addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } PendingIntent.getActivity( - context, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT + context, + System.currentTimeMillis().toInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ).let { setContentIntent(it) } @@ -587,8 +606,12 @@ class NotificationUtils @Inject constructor(private val context: Context, markRoomReadIntent.action = MARK_ROOM_READ_ACTION markRoomReadIntent.data = createIgnoredUri(roomInfo.roomId) markRoomReadIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId) - val markRoomReadPendingIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), markRoomReadIntent, - PendingIntent.FLAG_UPDATE_CURRENT) + val markRoomReadPendingIntent = PendingIntent.getBroadcast( + context, + System.currentTimeMillis().toInt(), + markRoomReadIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) NotificationCompat.Action.Builder(R.drawable.ic_material_done_all_white, stringProvider.getString(R.string.action_mark_room_read), markRoomReadPendingIntent) @@ -624,8 +647,12 @@ class NotificationUtils @Inject constructor(private val context: Context, val intent = Intent(context, NotificationBroadcastReceiver::class.java) intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId) intent.action = DISMISS_ROOM_NOTIF_ACTION - val pendingIntent = PendingIntent.getBroadcast(context.applicationContext, - System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) + val pendingIntent = PendingIntent.getBroadcast( + context.applicationContext, + System.currentTimeMillis().toInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) setDeleteIntent(pendingIntent) } .setTicker(tickerText) @@ -655,31 +682,41 @@ class NotificationUtils @Inject constructor(private val context: Context, rejectIntent.action = REJECT_ACTION rejectIntent.data = createIgnoredUri("$roomId&$matrixId") rejectIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) - val rejectIntentPendingIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), rejectIntent, - PendingIntent.FLAG_UPDATE_CURRENT) + val rejectIntentPendingIntent = PendingIntent.getBroadcast( + context, + System.currentTimeMillis().toInt(), + rejectIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) addAction( R.drawable.vector_notification_reject_invitation, stringProvider.getString(R.string.reject), - rejectIntentPendingIntent) + rejectIntentPendingIntent + ) // offer to type a quick accept button val joinIntent = Intent(context, NotificationBroadcastReceiver::class.java) joinIntent.action = JOIN_ACTION joinIntent.data = createIgnoredUri("$roomId&$matrixId") joinIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) - val joinIntentPendingIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), joinIntent, - PendingIntent.FLAG_UPDATE_CURRENT) + val joinIntentPendingIntent = PendingIntent.getBroadcast( + context, + System.currentTimeMillis().toInt(), + joinIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) addAction( R.drawable.vector_notification_accept_invitation, stringProvider.getString(R.string.join), - joinIntentPendingIntent) + joinIntentPendingIntent + ) val contentIntent = HomeActivity.newIntent(context, inviteNotificationRoomId = inviteNotifiableEvent.roomId) contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that contentIntent.data = createIgnoredUri(inviteNotifiableEvent.eventId) - setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) + setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, PendingIntentCompat.FLAG_IMMUTABLE)) if (inviteNotifiableEvent.noisy) { // Compat @@ -718,7 +755,7 @@ class NotificationUtils @Inject constructor(private val context: Context, contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that contentIntent.data = createIgnoredUri(simpleNotifiableEvent.eventId) - setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) + setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, PendingIntentCompat.FLAG_IMMUTABLE)) if (simpleNotifiableEvent.noisy) { // Compat @@ -745,14 +782,22 @@ class NotificationUtils @Inject constructor(private val context: Context, return TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(roomIntentTap) - .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) + .getPendingIntent( + System.currentTimeMillis().toInt(), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) } private fun buildOpenHomePendingIntentForSummary(): PendingIntent { val intent = HomeActivity.newIntent(context, clearNotification = true) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP intent.data = createIgnoredUri("tapSummary") - return PendingIntent.getActivity(context, Random.nextInt(1000), intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getActivity( + context, + Random.nextInt(1000), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) } /* @@ -769,8 +814,13 @@ class NotificationUtils @Inject constructor(private val context: Context, intent.action = SMART_REPLY_ACTION intent.data = createIgnoredUri(roomId) intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) - return PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), intent, - PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast( + context, + System.currentTimeMillis().toInt(), + intent, + // PendingIntents attached to actions with remote inputs must be mutable + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE + ) } else { /* TODO @@ -783,7 +833,7 @@ class NotificationUtils @Inject constructor(private val context: Context, // the action must be unique else the parameters are ignored quickReplyIntent.action = QUICK_LAUNCH_ACTION quickReplyIntent.data = createIgnoredUri($roomId") - return PendingIntent.getActivity(context, 0, quickReplyIntent, 0) + return PendingIntent.getActivity(context, 0, quickReplyIntent, PendingIntentCompat.FLAG_IMMUTABLE) } */ } @@ -837,8 +887,12 @@ class NotificationUtils @Inject constructor(private val context: Context, val intent = Intent(context, NotificationBroadcastReceiver::class.java) intent.action = DISMISS_SUMMARY_ACTION intent.data = createIgnoredUri("deleteSummary") - return PendingIntent.getBroadcast(context.applicationContext, - 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getBroadcast( + context.applicationContext, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) } fun showNotificationMessage(tag: String?, id: Int, notification: Notification) { @@ -875,7 +929,7 @@ class NotificationUtils @Inject constructor(private val context: Context, context, 0, testActionIntent, - PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) notificationManager.notify( diff --git a/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt b/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt index 9c55b88805..f848a3174e 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt @@ -17,11 +17,10 @@ package im.vector.app.features.pin import android.os.SystemClock -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.OnLifecycleEvent import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -41,7 +40,7 @@ private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L class PinLocker @Inject constructor( private val pinCodeStore: PinCodeStore, private val vectorPreferences: VectorPreferences -) : LifecycleObserver { +) : DefaultLifecycleObserver { enum class State { // App is locked, can be unlock @@ -87,16 +86,14 @@ class PinLocker @Inject constructor( computeState() } - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - fun entersForeground() { + override fun onResume(owner: LifecycleOwner) { val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= getGracePeriod() Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background shouldBeLocked: $shouldBeLocked") computeState() } - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) - fun entersBackground() { + override fun onPause(owner: LifecycleOwner) { Timber.v("App enters background") entersBackgroundTs = SystemClock.elapsedRealtime() } diff --git a/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt b/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt index 93c72ea69e..b4dcb07349 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt @@ -46,7 +46,7 @@ class RageShake @Inject constructor(private val activity: FragmentActivity, shakeDetector = ShakeDetector(this).apply { setSensitivity(vectorPreferences.getRageshakeSensitivity()) - start(sensorManager) + start(sensorManager, SensorManager.SENSOR_DELAY_GAME) } } diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index 786920aa22..1a91c00e11 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -23,7 +23,7 @@ import java.io.File import java.io.FileOutputStream abstract class AbstractVoiceRecorder( - context: Context, + private val context: Context, private val filenameExt: String ) : VoiceRecorder { private val outputDirectory: File by lazy { @@ -39,7 +39,7 @@ abstract class AbstractVoiceRecorder( abstract fun convertFile(recordedFile: File?): File? private fun init() { - MediaRecorder().let { + createMediaRecorder().let { it.setAudioSource(MediaRecorder.AudioSource.DEFAULT) setOutputFormat(it) it.setAudioEncodingBitRate(24000) @@ -48,6 +48,15 @@ abstract class AbstractVoiceRecorder( } } + private fun createMediaRecorder(): MediaRecorder { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaRecorder(context) + } else { + @Suppress("DEPRECATION") + MediaRecorder() + } + } + override fun startRecord() { init() outputFile = File(outputDirectory, "Voice message.$filenameExt")