Merge pull request #4433 from vector-im/feature/bma/android12

Android12
This commit is contained in:
Benoit Marty 2021-11-16 13:27:33 +01:00 committed by GitHub
commit be3aafeef2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 389 additions and 263 deletions

View file

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

View file

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

View file

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

View file

@ -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<Listener>()
private val listeners = LinkedHashSet<Listener>()
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() }

View file

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

View file

@ -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<MultiPickerBaseType>() {
// 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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,9 @@
<application>
<receiver android:name=".fdroid.receiver.OnApplicationUpgradeOrRebootReceiver">
<receiver
android:name=".fdroid.receiver.OnApplicationUpgradeOrRebootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />

View file

@ -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<AlarmManager>()!!
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<AlarmManager>()!!
alarmMgr.cancel(pIntent)

View file

@ -9,7 +9,9 @@
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<service android:name=".gplay.push.fcm.VectorFirebaseMessagingService">
<service
android:name=".gplay.push.fcm.VectorFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>

View file

@ -4,7 +4,10 @@
package="im.vector.app">
<!-- Needed for VOIP call to detect and switch to headset-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -418,6 +421,22 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/sdk_provider_paths" />
</provider>
<!-- Temporary fix for Android 12. android:exported has to be explicitly set when targeting Android 12
Do it for services coming from dependencies - BEGIN -->
<service
android:name="org.jitsi.meet.sdk.ConnectionService"
android:exported="false"
tools:node="merge" />
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:exported="false"
tools:node="merge" />
<service
android:name="androidx.sharetarget.ChooserTargetServiceCompat"
android:exported="false"
tools:node="merge" />
<!-- Temporary fix for Android 12 change - END -->
</application>
</manifest>

View file

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

View file

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

View file

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

View file

@ -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('/')
}

View file

@ -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 <T> LifecycleOwner.lifecycleAwareLazy(initializer: () -> T): Lazy<T> = LifecycleAwareLazy(this, initializer)
private object UninitializedValue
class LifecycleAwareLazy<out T>(
private val owner: LifecycleOwner,
initializer: () -> T
) : Lazy<T>, LifecycleObserver {
private val owner: LifecycleOwner,
initializer: () -> T
) : Lazy<T>, 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."
}

View file

@ -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<TelephonyManager>()
?: 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
}
}

View file

@ -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<VB : ViewBinding> : 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

View file

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

View file

@ -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<String, Any>) {
}
}
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)

View file

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

View file

@ -29,13 +29,8 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor(
private val stringProvider: StringProvider
) : ViewModel() {
var recoveryCode: MutableLiveData<String> = MutableLiveData()
var recoveryCodeErrorText: MutableLiveData<String> = MutableLiveData()
init {
recoveryCode.value = null
recoveryCodeErrorText.value = null
}
var recoveryCode: MutableLiveData<String?> = MutableLiveData(null)
var recoveryCodeErrorText: MutableLiveData<String?> = MutableLiveData(null)
// ========= Actions =========
fun updateCode(newValue: String) {

View file

@ -28,13 +28,8 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor(
private val stringProvider: StringProvider
) : ViewModel() {
var passphrase: MutableLiveData<String> = MutableLiveData()
var passphraseErrorText: MutableLiveData<String> = MutableLiveData()
init {
passphrase.value = null
passphraseErrorText.value = null
}
var passphrase: MutableLiveData<String?> = MutableLiveData(null)
var passphraseErrorText: MutableLiveData<String?> = MutableLiveData(null)
// ========= Actions =========

View file

@ -57,7 +57,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
lateinit var session: Session
var keyVersionResult: MutableLiveData<KeysVersionResult> = MutableLiveData()
var keyVersionResult: MutableLiveData<KeysVersionResult?> = MutableLiveData(null)
var keySourceModel: MutableLiveData<KeySource> = MutableLiveData()
@ -69,17 +69,11 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
val navigateEvent: LiveData<LiveEvent<String>>
get() = _navigateEvent
var loadingEvent: MutableLiveData<WaitingViewData> = MutableLiveData()
var loadingEvent: MutableLiveData<WaitingViewData?> = MutableLiveData(null)
var importKeyResult: ImportRoomKeysResult? = null
var importRoomKeysFinishWithResult: MutableLiveData<LiveEvent<ImportRoomKeysResult>> = MutableLiveData()
init {
keyVersionResult.value = null
_keyVersionResultError.value = null
loadingEvent.value = null
}
fun initSession(session: Session) {
this.session = session
}

View file

@ -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<Long> = MutableLiveData()
var recoveryKey: MutableLiveData<String> = MutableLiveData()
var prepareRecoverFailError: MutableLiveData<Throwable> = MutableLiveData()
var recoveryKey: MutableLiveData<String?> = MutableLiveData(null)
var prepareRecoverFailError: MutableLiveData<Throwable?> = MutableLiveData(null)
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
var copyHasBeenMade = false
var isCreatingBackupVersion: MutableLiveData<Boolean> = MutableLiveData()
var creatingBackupError: MutableLiveData<Throwable> = MutableLiveData()
var isCreatingBackupVersion: MutableLiveData<Boolean> = MutableLiveData(false)
var creatingBackupError: MutableLiveData<Throwable?> = MutableLiveData(null)
var keysVersion: MutableLiveData<KeysVersion> = MutableLiveData()
var loadingStatus: MutableLiveData<WaitingViewData> = MutableLiveData()
init {
recoveryKey.value = null
isCreatingBackupVersion.value = false
prepareRecoverFailError.value = null
creatingBackupError.value = null
loadingStatus.value = null
}
var loadingStatus: MutableLiveData<WaitingViewData?> = MutableLiveData(null)
fun initSession(session: Session) {
this.session = session

View file

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

View file

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

View file

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

View file

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

View file

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