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 package im.vector.lib.attachmentviewer
import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle 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 // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.setDecorFitsSystemWindows(false) window.setDecorFitsSystemWindows(false)
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE // 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 // New API instead of FLAG_TRANSLUCENT_STATUS
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
// new API instead of FLAG_TRANSLUCENT_NAVIGATION // 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 // new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars())
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE // 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 // New API instead of FLAG_TRANSLUCENT_STATUS
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
// New API instead of FLAG_TRANSLUCENT_NAVIGATION // New API instead of FLAG_TRANSLUCENT_NAVIGATION

View File

@ -1,8 +1,8 @@
ext.versions = [ ext.versions = [
'minSdk' : 21, 'minSdk' : 21,
'compileSdk' : 30, 'compileSdk' : 31,
'targetSdk' : 30, 'targetSdk' : 31,
'sourceCompat' : JavaVersion.VERSION_11, 'sourceCompat' : JavaVersion.VERSION_11,
'targetCompat' : JavaVersion.VERSION_11, 'targetCompat' : JavaVersion.VERSION_11,
] ]
@ -16,7 +16,7 @@ def retrofit = "2.9.0"
def arrow = "0.8.2" def arrow = "0.8.2"
def markwon = "4.6.2" def markwon = "4.6.2"
def moshi = "1.12.0" def moshi = "1.12.0"
def lifecycle = "2.2.0" def lifecycle = "2.4.0"
def flowBinding = "1.2.0" def flowBinding = "1.2.0"
def epoxy = "4.6.2" def epoxy = "4.6.2"
def mavericks = "2.4.0" def mavericks = "2.4.0"
@ -46,18 +46,18 @@ ext.libs = [
], ],
androidx : [ androidx : [
'appCompat' : "androidx.appcompat:appcompat:1.3.1", '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", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6", 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6",
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.1", '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", 'autoFill' : "androidx.autofill:autofill:1.1.0",
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1", 'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
'junit' : "androidx.test.ext:junit:1.1.3", 'junit' : "androidx.test.ext:junit:1.1.3",
'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle", 'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle",
'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle", 'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle",
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1", 'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle",
'datastore' : "androidx.datastore:datastore:1.0.0", 'datastore' : "androidx.datastore:datastore:1.0.0",
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0", 'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2", 'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",

View File

@ -44,6 +44,7 @@ android {
} }
testOptions { testOptions {
// Comment to run on Android 12
execution 'ANDROIDX_TEST_ORCHESTRATOR' execution 'ANDROIDX_TEST_ORCHESTRATOR'
} }
@ -106,8 +107,9 @@ dependencies {
implementation libs.androidx.appCompat implementation libs.androidx.appCompat
implementation libs.androidx.core implementation libs.androidx.core
implementation libs.androidx.lifecycleExtensions // Lifecycle
implementation libs.androidx.lifecycleJava8 implementation libs.androidx.lifecycleCommon
implementation libs.androidx.lifecycleProcess
// Network // Network
implementation libs.squareup.retrofit implementation libs.squareup.retrofit

View File

@ -16,9 +16,8 @@
package org.matrix.android.sdk.internal.util package org.matrix.android.sdk.internal.util
import androidx.lifecycle.Lifecycle import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.di.MatrixScope
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -27,13 +26,12 @@ import javax.inject.Inject
* To be attached to ProcessLifecycleOwner lifecycle * To be attached to ProcessLifecycleOwner lifecycle
*/ */
@MatrixScope @MatrixScope
internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObserver { internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecycleObserver {
var isInBackground: Boolean = true var isInBackground: Boolean = true
private set private set
private private val listeners = LinkedHashSet<Listener>()
val listeners = LinkedHashSet<Listener>()
fun register(listener: Listener) { fun register(listener: Listener) {
listeners.add(listener) listeners.add(listener)
@ -43,15 +41,13 @@ internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObse
listeners.remove(listener) listeners.remove(listener)
} }
@OnLifecycleEvent(Lifecycle.Event.ON_START) override fun onStart(owner: LifecycleOwner) {
fun onMoveToForeground() {
Timber.v("App returning to foreground…") Timber.v("App returning to foreground…")
isInBackground = false isInBackground = false
listeners.forEach { it.onMoveToForeground() } listeners.forEach { it.onMoveToForeground() }
} }
@OnLifecycleEvent(Lifecycle.Event.ON_STOP) override fun onStop(owner: LifecycleOwner) {
fun onMoveToBackground() {
Timber.v("App going to background…") Timber.v("App going to background…")
isInBackground = true isInBackground = true
listeners.forEach { it.onMoveToBackground() } listeners.forEach { it.onMoveToBackground() }

View File

@ -21,6 +21,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.provider.ContactsContract import android.provider.ContactsContract
import im.vector.lib.multipicker.entity.MultiPickerContactType import im.vector.lib.multipicker.entity.MultiPickerContactType
import im.vector.lib.multipicker.utils.getColumnIndexOrNull
/** /**
* Contact Picker implementation * Contact Picker implementation
@ -49,9 +50,9 @@ class ContactPicker : Picker<MultiPickerContactType>() {
null null
)?.use { cursor -> )?.use { cursor ->
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID) val idColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts._ID) ?: return@use
val nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) val nameColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.DISPLAY_NAME) ?: return@use
val photoUriColumn = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI) val photoUriColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.PHOTO_URI) ?: return@use
val contactId = cursor.getInt(idColumn) val contactId = cursor.getInt(idColumn)
var name = cursor.getString(nameColumn) var name = cursor.getString(nameColumn)
@ -72,10 +73,13 @@ class ContactPicker : Picker<MultiPickerContactType>() {
selection, selection,
selectionArgs, selectionArgs,
null null
)?.use { cursor -> )?.use inner@{ innerCursor ->
while (cursor.moveToNext()) { val mimeTypeColumnIndex = innerCursor.getColumnIndexOrNull(ContactsContract.Data.MIMETYPE) ?: return@inner
val mimeType = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)) val data1ColumnIndex = innerCursor.getColumnIndexOrNull(ContactsContract.Data.DATA1) ?: return@inner
val contactData = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1))
while (innerCursor.moveToNext()) {
val mimeType = innerCursor.getString(mimeTypeColumnIndex)
val contactData = innerCursor.getString(data1ColumnIndex)
if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) { if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) {
name = contactData name = contactData
@ -115,7 +119,10 @@ class ContactPicker : Picker<MultiPickerContactType>() {
selectionArgs, selectionArgs,
null null
)?.use { cursor -> )?.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 android.provider.OpenableColumns
import im.vector.lib.multipicker.entity.MultiPickerBaseType import im.vector.lib.multipicker.entity.MultiPickerBaseType
import im.vector.lib.multipicker.entity.MultiPickerFileType 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.isMimeTypeAudio
import im.vector.lib.multipicker.utils.isMimeTypeImage import im.vector.lib.multipicker.utils.isMimeTypeImage
import im.vector.lib.multipicker.utils.isMimeTypeVideo import im.vector.lib.multipicker.utils.isMimeTypeVideo
@ -49,8 +50,8 @@ class FilePicker : Picker<MultiPickerBaseType>() {
// Other files // Other files
context.contentResolver.query(selectedUri, null, null, null, null) context.contentResolver.query(selectedUri, null, null, null, null)
?.use { cursor -> ?.use { cursor ->
val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) val nameColumn = cursor.getColumnIndexOrNull(OpenableColumns.DISPLAY_NAME) ?: return@use null
val sizeColumn = cursor.getColumnIndex(OpenableColumns.SIZE) val sizeColumn = cursor.getColumnIndexOrNull(OpenableColumns.SIZE) ?: return@use null
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val name = cursor.getString(nameColumn) val name = cursor.getString(nameColumn)
val size = cursor.getLong(sizeColumn) val size = cursor.getLong(sizeColumn)

View File

@ -37,8 +37,8 @@ internal fun Uri.toMultiPickerImageType(context: Context): MultiPickerImageType?
null, null,
null null
)?.use { cursor -> )?.use { cursor ->
val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Images.Media.DISPLAY_NAME) ?: return@use null
val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE) val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Images.Media.SIZE) ?: return@use null
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
val name = cursor.getString(nameColumn) val name = cursor.getString(nameColumn)
@ -75,8 +75,8 @@ internal fun Uri.toMultiPickerVideoType(context: Context): MultiPickerVideoType?
null, null,
null null
)?.use { cursor -> )?.use { cursor ->
val nameColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME) val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Video.Media.DISPLAY_NAME) ?: return@use null
val sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE) val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Video.Media.SIZE) ?: return@use null
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
val name = cursor.getString(nameColumn) val name = cursor.getString(nameColumn)
@ -124,8 +124,8 @@ fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? {
null, null,
null null
)?.use { cursor -> )?.use { cursor ->
val nameColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME) val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Audio.Media.DISPLAY_NAME) ?: return@use null
val sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE) val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Audio.Media.SIZE) ?: return@use null
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
val name = cursor.getString(nameColumn) 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 PARAM_APK=$2
# Other params # Other params
BUILD_TOOLS_VERSION="30.0.3" BUILD_TOOLS_VERSION="31.0.0-rc5"
MIN_SDK_VERSION=21 MIN_SDK_VERSION=21
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..." 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 PARAM_KEY_PASS=$4
# Other params # Other params
BUILD_TOOLS_VERSION="30.0.3" BUILD_TOOLS_VERSION="31.0.0-rc5"
MIN_SDK_VERSION=21 MIN_SDK_VERSION=21
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..." 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. // This property does not affect tests that you run using Android Studio.
animationsDisabled = true animationsDisabled = true
// Comment to run on Android 12
execution 'ANDROIDX_TEST_ORCHESTRATOR' execution 'ANDROIDX_TEST_ORCHESTRATOR'
} }
@ -356,8 +357,10 @@ dependencies {
implementation libs.squareup.moshi implementation libs.squareup.moshi
kapt libs.squareup.moshiKotlin kapt libs.squareup.moshiKotlin
implementation libs.androidx.lifecycleExtensions
// Lifecycle
implementation libs.androidx.lifecycleLivedata implementation libs.androidx.lifecycleLivedata
implementation libs.androidx.lifecycleProcess
implementation libs.androidx.datastore implementation libs.androidx.datastore
implementation libs.androidx.datastorepreferences implementation libs.androidx.datastorepreferences
@ -411,7 +414,7 @@ dependencies {
implementation 'com.github.Armen101:AudioRecordView:1.0.5' implementation 'com.github.Armen101:AudioRecordView:1.0.5'
// Custom Tab // Custom Tab
implementation 'androidx.browser:browser:1.3.0' implementation 'androidx.browser:browser:1.4.0'
// Passphrase strength helper // Passphrase strength helper
implementation 'com.nulab-inc:zxcvbn:1.5.2' implementation 'com.nulab-inc:zxcvbn:1.5.2'

View File

@ -20,6 +20,7 @@ import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 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.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) 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( useMediaStoreScreenshotStorage(
contentValues, contentValues,
contentResolver, contentResolver,
@ -90,6 +91,7 @@ private fun useMediaStoreScreenshotStorage(
} }
} }
@Suppress("DEPRECATION")
private fun usePublicExternalScreenshotStorage( private fun usePublicExternalScreenshotStorage(
contentValues: ContentValues, contentValues: ContentValues,
contentResolver: ContentResolver, contentResolver: ContentResolver,

View File

@ -14,7 +14,9 @@
<application> <application>
<receiver android:name=".fdroid.receiver.OnApplicationUpgradeOrRebootReceiver"> <receiver
android:name=".fdroid.receiver.OnApplicationUpgradeOrRebootReceiver"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" /> <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.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.platform.PendingIntentCompat
import im.vector.app.core.services.VectorSyncService import im.vector.app.core.services.VectorSyncService
import org.matrix.android.sdk.internal.session.sync.job.SyncService import org.matrix.android.sdk.internal.session.sync.job.SyncService
import timber.log.Timber import timber.log.Timber
@ -67,7 +68,12 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
putExtra(SyncService.EXTRA_SESSION_ID, sessionId) putExtra(SyncService.EXTRA_SESSION_ID, sessionId)
putExtra(SyncService.EXTRA_PERIODIC, true) 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 firstMillis = System.currentTimeMillis() + delayInSeconds * 1000L
val alarmMgr = context.getSystemService<AlarmManager>()!! val alarmMgr = context.getSystemService<AlarmManager>()!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -80,7 +86,12 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
fun cancelAlarm(context: Context) { fun cancelAlarm(context: Context) {
Timber.v("## Sync: Cancel alarm for background sync") Timber.v("## Sync: Cancel alarm for background sync")
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) 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>()!! val alarmMgr = context.getSystemService<AlarmManager>()!!
alarmMgr.cancel(pIntent) alarmMgr.cancel(pIntent)

View File

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

View File

@ -4,7 +4,10 @@
package="im.vector.app"> package="im.vector.app">
<!-- Needed for VOIP call to detect and switch to headset--> <!-- 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.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -418,6 +421,22 @@
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/sdk_provider_paths" /> android:resource="@xml/sdk_provider_paths" />
</provider> </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> </application>
</manifest> </manifest>

View File

@ -16,9 +16,8 @@
package im.vector.app package im.vector.app
import androidx.lifecycle.Lifecycle import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import arrow.core.Option import arrow.core.Option
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.core.utils.BehaviorDataSource
@ -57,7 +56,7 @@ class AppStateHandler @Inject constructor(
private val sessionDataSource: ActiveSessionDataSource, private val sessionDataSource: ActiveSessionDataSource,
private val uiStateRepository: UiStateRepository, private val uiStateRepository: UiStateRepository,
private val activeSessionHolder: ActiveSessionHolder private val activeSessionHolder: ActiveSessionHolder
) : LifecycleObserver { ) : DefaultLifecycleObserver {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty()) 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 return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
} }
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) override fun onResume(owner: LifecycleOwner) {
fun entersForeground() {
observeActiveSession() observeActiveSession()
} }
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) override fun onPause(owner: LifecycleOwner) {
fun entersBackground() {
coroutineScope.coroutineContext.cancelChildren() coroutineScope.coroutineContext.cancelChildren()
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) {

View File

@ -27,9 +27,8 @@ import android.os.HandlerThread
import android.os.StrictMode import android.os.StrictMode
import androidx.core.provider.FontRequest import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat import androidx.core.provider.FontsContractCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyAsyncUtil
@ -166,9 +165,8 @@ class VectorApplication :
ProcessLifecycleOwner.get().lifecycle.addObserver(startSyncOnFirstStart) ProcessLifecycleOwner.get().lifecycle.addObserver(startSyncOnFirstStart)
ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver { ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) override fun onResume(owner: LifecycleOwner) {
fun entersForeground() {
Timber.i("App entered foreground") Timber.i("App entered foreground")
FcmHelper.onEnterForeground(appContext, activeSessionHolder) FcmHelper.onEnterForeground(appContext, activeSessionHolder)
activeSessionHolder.getSafeActiveSession()?.also { activeSessionHolder.getSafeActiveSession()?.also {
@ -176,8 +174,7 @@ class VectorApplication :
} }
} }
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) override fun onPause(owner: LifecycleOwner) {
fun entersBackground() {
Timber.i("App entered background") // call persistInfo Timber.i("App entered background") // call persistInfo
notificationDrawerManager.persistInfo() notificationDrawerManager.persistInfo()
FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder) FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder)
@ -198,9 +195,8 @@ class VectorApplication :
EmojiManager.install(GoogleEmojiProvider()) EmojiManager.install(GoogleEmojiProvider())
} }
private val startSyncOnFirstStart = object : LifecycleObserver { private val startSyncOnFirstStart = object : DefaultLifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START) override fun onStart(owner: LifecycleOwner) {
fun onStart() {
Timber.i("App process started") Timber.i("App process started")
authenticationService.getLastAuthenticatedSession()?.startSyncing(appContext) authenticationService.getLastAuthenticatedSession()?.startSyncing(appContext)
ProcessLifecycleOwner.get().lifecycle.removeObserver(this) ProcessLifecycleOwner.get().lifecycle.removeObserver(this)

View File

@ -17,10 +17,10 @@
package im.vector.app.core.contacts package im.vector.app.core.contacts
import android.content.Context import android.content.Context
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.provider.ContactsContract import android.provider.ContactsContract
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import im.vector.lib.multipicker.utils.getColumnIndexOrNull
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -57,16 +57,20 @@ class ContactsDataSource @Inject constructor(
) )
?.use { cursor -> ?.use { cursor ->
if (cursor.count > 0) { 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()) { while (cursor.moveToNext()) {
val id = cursor.getLong(ContactsContract.Contacts._ID) ?: continue val id = cursor.getLong(idColumnIndex)
val displayName = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) ?: continue val displayName = cursor.getString(displayNameColumnIndex)
val mappedContactBuilder = MappedContactBuilder( val mappedContactBuilder = MappedContactBuilder(
id = id, id = id,
displayName = displayName displayName = displayName
) )
cursor.getString(ContactsContract.Data.PHOTO_URI) photoUriColumnIndex
?.let { cursor.getString(it) }
?.let { Uri.parse(it) } ?.let { Uri.parse(it) }
?.let { mappedContactBuilder.photoURI = it } ?.let { mappedContactBuilder.photoURI = it }
@ -85,12 +89,15 @@ class ContactsDataSource @Inject constructor(
null, null,
null, null,
null) null)
?.use { innerCursor -> ?.use { cursor ->
while (innerCursor.moveToNext()) { val idColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) ?: return@use
val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) val phoneNumberColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Phone.NUMBER) ?: return@use
?.let { map[it] }
while (cursor.moveToNext()) {
val mappedContactBuilder = cursor.getLong(idColumnIndex)
.let { map[it] }
?: continue ?: continue
innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) cursor.getString(phoneNumberColumnIndex)
?.let { ?.let {
mappedContactBuilder.msisdns.add( mappedContactBuilder.msisdns.add(
MappedMsisdn( MappedMsisdn(
@ -114,14 +121,17 @@ class ContactsDataSource @Inject constructor(
null, null,
null, null,
null) null)
?.use { innerCursor -> ?.use { cursor ->
while (innerCursor.moveToNext()) { 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 // This would allow you get several email addresses
// if the email addresses were stored in an array // if the email addresses were stored in an array
val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Email.CONTACT_ID) val mappedContactBuilder = cursor.getLong(idColumnIndex)
?.let { map[it] } .let { map[it] }
?: continue ?: continue
innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) cursor.getString(emailColumnIndex)
?.let { ?.let {
mappedContactBuilder.emails.add( mappedContactBuilder.emails.add(
MappedEmail( MappedEmail(
@ -140,16 +150,4 @@ class ContactsDataSource @Inject constructor(
.filter { it.emails.isNotEmpty() || it.msisdns.isNotEmpty() } .filter { it.emails.isNotEmpty() || it.msisdns.isNotEmpty() }
.map { it.build() } .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.content.Context
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns import android.provider.OpenableColumns
import im.vector.lib.multipicker.utils.getColumnIndexOrNull
fun getFilenameFromUri(context: Context?, uri: Uri): String? { fun getFilenameFromUri(context: Context?, uri: Uri): String? {
if (context != null && uri.scheme == "content") { if (context != null && uri.scheme == "content") {
val cursor = context.contentResolver.query(uri, null, null, null, null) context.contentResolver.query(uri, null, null, null, null)
cursor?.use { ?.use { cursor ->
if (it.moveToFirst()) { if (cursor.moveToFirst()) {
return it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) return cursor.getColumnIndexOrNull(OpenableColumns.DISPLAY_NAME)
} ?.let { cursor.getString(it) }
} }
}
} }
return uri.path?.substringAfterLast('/') return uri.path?.substringAfterLast('/')
} }

View File

@ -18,58 +18,56 @@ package im.vector.app.core.platform
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
fun <T> LifecycleOwner.lifecycleAwareLazy(initializer: () -> T): Lazy<T> = LifecycleAwareLazy(this, initializer) fun <T> LifecycleOwner.lifecycleAwareLazy(initializer: () -> T): Lazy<T> = LifecycleAwareLazy(this, initializer)
private object UninitializedValue private object UninitializedValue
class LifecycleAwareLazy<out T>( class LifecycleAwareLazy<out T>(
private val owner: LifecycleOwner, private val owner: LifecycleOwner,
initializer: () -> T initializer: () -> T
) : Lazy<T>, LifecycleObserver { ) : 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") @Suppress("UNCHECKED_CAST")
override val value: T override val value: T
@MainThread @MainThread
get() { get() {
if (_value === UninitializedValue) { if (_value === UninitializedValue) {
_value = initializer!!() _value = initializer!!()
attachToLifecycle() attachToLifecycle()
} }
return _value as T return _value as T
}
override fun onDestroy(owner: LifecycleOwner) {
_value = UninitializedValue
detachFromLifecycle()
} }
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) private fun attachToLifecycle() {
fun resetValue() { if (getLifecycleOwner().lifecycle.currentState == Lifecycle.State.DESTROYED) {
_value = UninitializedValue throw IllegalStateException("Initialization failed because lifecycle has been destroyed!")
detachFromLifecycle() }
} getLifecycleOwner().lifecycle.addObserver(this)
private fun attachToLifecycle() {
if (getLifecycleOwner().lifecycle.currentState == Lifecycle.State.DESTROYED) {
throw IllegalStateException("Initialization failed because lifecycle has been destroyed!")
} }
getLifecycleOwner().lifecycle.addObserver(this)
}
private fun detachFromLifecycle() { private fun detachFromLifecycle() {
getLifecycleOwner().lifecycle.removeObserver(this) getLifecycleOwner().lifecycle.removeObserver(this)
} }
private fun getLifecycleOwner() = when (owner) { private fun getLifecycleOwner() = when (owner) {
is Fragment -> owner.viewLifecycleOwner is Fragment -> owner.viewLifecycleOwner
else -> owner 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,17 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.features.call.telecom package im.vector.app.core.platform
import android.content.Context import android.app.PendingIntent
import android.telephony.TelephonyManager import android.os.Build
import androidx.core.content.getSystemService
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 FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val telephonyManager = context.getSystemService<TelephonyManager>() PendingIntent.FLAG_MUTABLE
?: return false } else {
return telephonyManager.callState != TelephonyManager.CALL_STATE_IDLE 0
} }
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.core.platform package im.vector.app.core.platform
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.res.Configuration 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 // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.setDecorFitsSystemWindows(false) window.setDecorFitsSystemWindows(false)
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE // 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 // New API instead of FLAG_TRANSLUCENT_STATUS
window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.attachmentviewer.R.color.half_transparent_status_bar) window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.attachmentviewer.R.color.half_transparent_status_bar)
// New API instead of FLAG_TRANSLUCENT_NAVIGATION // New API instead of FLAG_TRANSLUCENT_NAVIGATION

View File

@ -32,6 +32,7 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.PendingIntentCompat
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.BackgroundSyncMode
import org.matrix.android.sdk.internal.session.sync.job.SyncService import org.matrix.android.sdk.internal.session.sync.job.SyncService
@ -199,9 +200,9 @@ private fun Context.rescheduleSyncService(sessionId: String,
startService(intent) startService(intent)
} else { } else {
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 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 { } else {
PendingIntent.getService(this, 0, intent, 0) PendingIntent.getService(this, 0, intent, PendingIntentCompat.FLAG_IMMUTABLE)
} }
val firstMillis = System.currentTimeMillis() + syncDelaySeconds * 1000L val firstMillis = System.currentTimeMillis() + syncDelaySeconds * 1000L
val alarmMgr = getSystemService<AlarmManager>()!! val alarmMgr = getSystemService<AlarmManager>()!!

View File

@ -20,9 +20,8 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import androidx.lifecycle.Lifecycle import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.JavaOnlyMap
import org.jitsi.meet.sdk.BroadcastEmitter 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() { fun emitConferenceEnded() {
val broadcastEventData = JavaOnlyMap.of(CONFERENCE_URL_DATA_KEY, JitsiMeet.getCurrentConference()) 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, class ConferenceEventObserver(private val context: Context,
private val onBroadcastEvent: (ConferenceEvent) -> Unit) : private val onBroadcastEvent: (ConferenceEvent) -> Unit) :
LifecycleObserver { DefaultLifecycleObserver {
// See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events // See https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk#listening-for-broadcasted-events
private val broadcastReceiver = object : BroadcastReceiver() { private val broadcastReceiver = object : BroadcastReceiver() {
@ -61,8 +60,7 @@ class ConferenceEventObserver(private val context: Context,
} }
} }
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) override fun onDestroy(owner: LifecycleOwner) {
fun unregisterForBroadcastMessages() {
try { try {
LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver) LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver)
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
@ -70,8 +68,7 @@ class ConferenceEventObserver(private val context: Context,
} }
} }
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE) override fun onCreate(owner: LifecycleOwner) {
fun registerForBroadcastMessages() {
val intentFilter = IntentFilter() val intentFilter = IntentFilter()
for (type in BroadcastEvent.Type.values()) { for (type in BroadcastEvent.Type.values()) {
intentFilter.addAction(type.action) intentFilter.addAction(type.action)

View File

@ -17,9 +17,8 @@
package im.vector.app.features.call.webrtc package im.vector.app.features.call.webrtc
import android.content.Context import android.content.Context
import androidx.lifecycle.Lifecycle import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import im.vector.app.ActiveSessionDataSource import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.core.services.CallService import im.vector.app.core.services.CallService
@ -70,7 +69,8 @@ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP)
class WebRtcCallManager @Inject constructor( class WebRtcCallManager @Inject constructor(
private val context: Context, private val context: Context,
private val activeSessionDataSource: ActiveSessionDataSource private val activeSessionDataSource: ActiveSessionDataSource
) : CallListener, LifecycleObserver { ) : CallListener,
DefaultLifecycleObserver {
private val currentSession: Session? private val currentSession: Session?
get() = activeSessionDataSource.currentValue?.orNull() get() = activeSessionDataSource.currentValue?.orNull()
@ -133,13 +133,11 @@ class WebRtcCallManager @Inject constructor(
private var isInBackground: Boolean = true private var isInBackground: Boolean = true
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) override fun onResume(owner: LifecycleOwner) {
fun entersForeground() {
isInBackground = false isInBackground = false
} }
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) override fun onPause(owner: LifecycleOwner) {
fun entersBackground() {
isInBackground = true isInBackground = true
} }

View File

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

View File

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

View File

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

View File

@ -68,23 +68,15 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
// Step 3 // Step 3
// Var to ignore events from previous request(s) to generate a recovery key // Var to ignore events from previous request(s) to generate a recovery key
private var currentRequestId: MutableLiveData<Long> = MutableLiveData() private var currentRequestId: MutableLiveData<Long> = MutableLiveData()
var recoveryKey: MutableLiveData<String> = MutableLiveData() var recoveryKey: MutableLiveData<String?> = MutableLiveData(null)
var prepareRecoverFailError: MutableLiveData<Throwable> = MutableLiveData() var prepareRecoverFailError: MutableLiveData<Throwable?> = MutableLiveData(null)
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
var copyHasBeenMade = false var copyHasBeenMade = false
var isCreatingBackupVersion: MutableLiveData<Boolean> = MutableLiveData() var isCreatingBackupVersion: MutableLiveData<Boolean> = MutableLiveData(false)
var creatingBackupError: MutableLiveData<Throwable> = MutableLiveData() var creatingBackupError: MutableLiveData<Throwable?> = MutableLiveData(null)
var keysVersion: MutableLiveData<KeysVersion> = MutableLiveData() var keysVersion: MutableLiveData<KeysVersion> = MutableLiveData()
var loadingStatus: MutableLiveData<WaitingViewData> = MutableLiveData() var loadingStatus: MutableLiveData<WaitingViewData?> = MutableLiveData(null)
init {
recoveryKey.value = null
isCreatingBackupVersion.value = false
prepareRecoverFailError.value = null
creatingBackupError.value = null
loadingStatus.value = null
}
fun initSession(session: Session) { fun initSession(session: Session) {
this.session = session this.session = session

View File

@ -17,13 +17,15 @@
package im.vector.app.features.home.room.detail.composer package im.vector.app.features.home.room.detail.composer
import android.content.ClipData
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build
import android.text.Editable import android.text.Editable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection 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.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat import androidx.core.view.inputmethod.InputConnectionCompat
import com.vanniktech.emoji.EmojiEditText import com.vanniktech.emoji.EmojiEditText
@ -33,7 +35,7 @@ import im.vector.app.features.html.PillImageSpan
import timber.log.Timber import timber.log.Timber
class ComposerEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle) : 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 { interface Callback {
fun onRichContentSelected(contentUri: Uri): Boolean fun onRichContentSelected(contentUri: Uri): Boolean
@ -43,23 +45,35 @@ class ComposerEditText @JvmOverloads constructor(context: Context, attrs: Attrib
var callback: Callback? = null var callback: Callback? = null
override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection? { override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection? {
val ic = super.onCreateInputConnection(editorInfo) ?: return null var ic = super.onCreateInputConnection(editorInfo) ?: return null
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("*/*")) val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this) ?: arrayOf("image/*")
val callback = EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes)
InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, _ -> ic = InputConnectionCompat.createWrapper(this, ic, editorInfo)
val lacksPermission = (flags and
InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0 val onReceiveContentListener = OnReceiveContentListener { _, payload ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) { val split = payload.partition { item -> item.uri != null }
try { val uriContent = split.first
inputContentInfo.requestPermission() val remaining = split.second
} catch (e: Exception) {
return@OnCommitContentListener false if (uriContent != null) {
} val clip: ClipData = uriContent.clip
} for (i in 0 until clip.itemCount) {
callback?.onRichContentSelected(inputContentInfo.contentUri) ?: false 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 { init {

View File

@ -48,6 +48,7 @@ import androidx.fragment.app.Fragment
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.createIgnoredUri 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.resources.StringProvider
import im.vector.app.core.services.CallService import im.vector.app.core.services.CallService
import im.vector.app.core.utils.startNotificationChannelSettingsIntent 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. // build the pending intent go to the home screen if this is clicked.
val i = HomeActivity.newIntent(context) val i = HomeActivity.newIntent(context)
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 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) 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 flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
data = createIgnoredUri(call.callId) 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) val answerCallPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(VectorCallActivity.newIntent( .addNextIntent(
context = context, VectorCallActivity.newIntent(
call = call, context = context,
mode = VectorCallActivity.INCOMING_ACCEPT) 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) 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) IconCompat.createWithResource(context, R.drawable.ic_call_hangup)
.setTint(ThemeUtils.getColor(context, R.attr.colorError)), .setTint(ThemeUtils.getColor(context, R.attr.colorError)),
getActionText(R.string.call_notification_reject, R.attr.colorError), getActionText(R.string.call_notification_reject, R.attr.colorError),
rejectCallPendingIntent) rejectCallPendingIntent
)
) )
builder.addAction( 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 flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
data = createIgnoredUri(call.callId) 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) 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) IconCompat.createWithResource(context, R.drawable.ic_call_hangup)
.setTint(ThemeUtils.getColor(context, R.attr.colorError)), .setTint(ThemeUtils.getColor(context, R.attr.colorError)),
getActionText(R.string.call_notification_hangup, R.attr.colorError), getActionText(R.string.call_notification_hangup, R.attr.colorError),
rejectCallPendingIntent) rejectCallPendingIntent
)
) )
builder.setContentIntent(contentPendingIntent) builder.setContentIntent(contentPendingIntent)
@ -431,13 +446,14 @@ class NotificationUtils @Inject constructor(private val context: Context,
IconCompat.createWithResource(context, R.drawable.ic_call_hangup) IconCompat.createWithResource(context, R.drawable.ic_call_hangup)
.setTint(ThemeUtils.getColor(context, R.attr.colorError)), .setTint(ThemeUtils.getColor(context, R.attr.colorError)),
getActionText(R.string.call_notification_hangup, R.attr.colorError), getActionText(R.string.call_notification_hangup, R.attr.colorError),
rejectCallPendingIntent) rejectCallPendingIntent
)
) )
val contentPendingIntent = TaskStackBuilder.create(context) val contentPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(VectorCallActivity.newIntent(context, call, null)) .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) builder.setContentIntent(contentPendingIntent)
@ -453,7 +469,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
context, context,
System.currentTimeMillis().toInt(), System.currentTimeMillis().toInt(),
rejectCallActionReceiver, 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) val contentPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(RoomDetailActivity.newIntent(context, RoomDetailArgs(callInformation.nativeRoomId))) .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) builder.setContentIntent(contentPendingIntent)
return builder.build() return builder.build()
@ -517,7 +533,10 @@ class NotificationUtils @Inject constructor(private val context: Context,
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} }
PendingIntent.getActivity( 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 { ).let {
setContentIntent(it) setContentIntent(it)
} }
@ -587,8 +606,12 @@ class NotificationUtils @Inject constructor(private val context: Context,
markRoomReadIntent.action = MARK_ROOM_READ_ACTION markRoomReadIntent.action = MARK_ROOM_READ_ACTION
markRoomReadIntent.data = createIgnoredUri(roomInfo.roomId) markRoomReadIntent.data = createIgnoredUri(roomInfo.roomId)
markRoomReadIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId) markRoomReadIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId)
val markRoomReadPendingIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), markRoomReadIntent, val markRoomReadPendingIntent = PendingIntent.getBroadcast(
PendingIntent.FLAG_UPDATE_CURRENT) context,
System.currentTimeMillis().toInt(),
markRoomReadIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
NotificationCompat.Action.Builder(R.drawable.ic_material_done_all_white, NotificationCompat.Action.Builder(R.drawable.ic_material_done_all_white,
stringProvider.getString(R.string.action_mark_room_read), markRoomReadPendingIntent) 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) val intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId) intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId)
intent.action = DISMISS_ROOM_NOTIF_ACTION intent.action = DISMISS_ROOM_NOTIF_ACTION
val pendingIntent = PendingIntent.getBroadcast(context.applicationContext, val pendingIntent = PendingIntent.getBroadcast(
System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) context.applicationContext,
System.currentTimeMillis().toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
setDeleteIntent(pendingIntent) setDeleteIntent(pendingIntent)
} }
.setTicker(tickerText) .setTicker(tickerText)
@ -655,31 +682,41 @@ class NotificationUtils @Inject constructor(private val context: Context,
rejectIntent.action = REJECT_ACTION rejectIntent.action = REJECT_ACTION
rejectIntent.data = createIgnoredUri("$roomId&$matrixId") rejectIntent.data = createIgnoredUri("$roomId&$matrixId")
rejectIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) rejectIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
val rejectIntentPendingIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), rejectIntent, val rejectIntentPendingIntent = PendingIntent.getBroadcast(
PendingIntent.FLAG_UPDATE_CURRENT) context,
System.currentTimeMillis().toInt(),
rejectIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
addAction( addAction(
R.drawable.vector_notification_reject_invitation, R.drawable.vector_notification_reject_invitation,
stringProvider.getString(R.string.reject), stringProvider.getString(R.string.reject),
rejectIntentPendingIntent) rejectIntentPendingIntent
)
// offer to type a quick accept button // offer to type a quick accept button
val joinIntent = Intent(context, NotificationBroadcastReceiver::class.java) val joinIntent = Intent(context, NotificationBroadcastReceiver::class.java)
joinIntent.action = JOIN_ACTION joinIntent.action = JOIN_ACTION
joinIntent.data = createIgnoredUri("$roomId&$matrixId") joinIntent.data = createIgnoredUri("$roomId&$matrixId")
joinIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) joinIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
val joinIntentPendingIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), joinIntent, val joinIntentPendingIntent = PendingIntent.getBroadcast(
PendingIntent.FLAG_UPDATE_CURRENT) context,
System.currentTimeMillis().toInt(),
joinIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
addAction( addAction(
R.drawable.vector_notification_accept_invitation, R.drawable.vector_notification_accept_invitation,
stringProvider.getString(R.string.join), stringProvider.getString(R.string.join),
joinIntentPendingIntent) joinIntentPendingIntent
)
val contentIntent = HomeActivity.newIntent(context, inviteNotificationRoomId = inviteNotifiableEvent.roomId) val contentIntent = HomeActivity.newIntent(context, inviteNotificationRoomId = inviteNotifiableEvent.roomId)
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 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 // 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) contentIntent.data = createIgnoredUri(inviteNotifiableEvent.eventId)
setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, PendingIntentCompat.FLAG_IMMUTABLE))
if (inviteNotifiableEvent.noisy) { if (inviteNotifiableEvent.noisy) {
// Compat // 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 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 // 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) contentIntent.data = createIgnoredUri(simpleNotifiableEvent.eventId)
setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, PendingIntentCompat.FLAG_IMMUTABLE))
if (simpleNotifiableEvent.noisy) { if (simpleNotifiableEvent.noisy) {
// Compat // Compat
@ -745,14 +782,22 @@ class NotificationUtils @Inject constructor(private val context: Context,
return TaskStackBuilder.create(context) return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntentWithParentStack(HomeActivity.newIntent(context))
.addNextIntent(roomIntentTap) .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 { private fun buildOpenHomePendingIntentForSummary(): PendingIntent {
val intent = HomeActivity.newIntent(context, clearNotification = true) val intent = HomeActivity.newIntent(context, clearNotification = true)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
intent.data = createIgnoredUri("tapSummary") 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.action = SMART_REPLY_ACTION
intent.data = createIgnoredUri(roomId) intent.data = createIgnoredUri(roomId)
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
return PendingIntent.getBroadcast(context, System.currentTimeMillis().toInt(), intent, return PendingIntent.getBroadcast(
PendingIntent.FLAG_UPDATE_CURRENT) context,
System.currentTimeMillis().toInt(),
intent,
// PendingIntents attached to actions with remote inputs must be mutable
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE
)
} else { } else {
/* /*
TODO TODO
@ -783,7 +833,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
// the action must be unique else the parameters are ignored // the action must be unique else the parameters are ignored
quickReplyIntent.action = QUICK_LAUNCH_ACTION quickReplyIntent.action = QUICK_LAUNCH_ACTION
quickReplyIntent.data = createIgnoredUri($roomId") 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) val intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.action = DISMISS_SUMMARY_ACTION intent.action = DISMISS_SUMMARY_ACTION
intent.data = createIgnoredUri("deleteSummary") intent.data = createIgnoredUri("deleteSummary")
return PendingIntent.getBroadcast(context.applicationContext, return PendingIntent.getBroadcast(
0, intent, PendingIntent.FLAG_UPDATE_CURRENT) context.applicationContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
} }
fun showNotificationMessage(tag: String?, id: Int, notification: Notification) { fun showNotificationMessage(tag: String?, id: Int, notification: Notification) {
@ -875,7 +929,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
context, context,
0, 0,
testActionIntent, testActionIntent,
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
notificationManager.notify( notificationManager.notify(

View File

@ -17,11 +17,10 @@
package im.vector.app.features.pin package im.vector.app.features.pin
import android.os.SystemClock import android.os.SystemClock
import androidx.lifecycle.Lifecycle import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.OnLifecycleEvent
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -41,7 +40,7 @@ private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
class PinLocker @Inject constructor( class PinLocker @Inject constructor(
private val pinCodeStore: PinCodeStore, private val pinCodeStore: PinCodeStore,
private val vectorPreferences: VectorPreferences private val vectorPreferences: VectorPreferences
) : LifecycleObserver { ) : DefaultLifecycleObserver {
enum class State { enum class State {
// App is locked, can be unlock // App is locked, can be unlock
@ -87,16 +86,14 @@ class PinLocker @Inject constructor(
computeState() computeState()
} }
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) override fun onResume(owner: LifecycleOwner) {
fun entersForeground() {
val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= getGracePeriod() shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= getGracePeriod()
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background shouldBeLocked: $shouldBeLocked") Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background shouldBeLocked: $shouldBeLocked")
computeState() computeState()
} }
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) override fun onPause(owner: LifecycleOwner) {
fun entersBackground() {
Timber.v("App enters background") Timber.v("App enters background")
entersBackgroundTs = SystemClock.elapsedRealtime() entersBackgroundTs = SystemClock.elapsedRealtime()
} }

View File

@ -46,7 +46,7 @@ class RageShake @Inject constructor(private val activity: FragmentActivity,
shakeDetector = ShakeDetector(this).apply { shakeDetector = ShakeDetector(this).apply {
setSensitivity(vectorPreferences.getRageshakeSensitivity()) 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 import java.io.FileOutputStream
abstract class AbstractVoiceRecorder( abstract class AbstractVoiceRecorder(
context: Context, private val context: Context,
private val filenameExt: String private val filenameExt: String
) : VoiceRecorder { ) : VoiceRecorder {
private val outputDirectory: File by lazy { private val outputDirectory: File by lazy {
@ -39,7 +39,7 @@ abstract class AbstractVoiceRecorder(
abstract fun convertFile(recordedFile: File?): File? abstract fun convertFile(recordedFile: File?): File?
private fun init() { private fun init() {
MediaRecorder().let { createMediaRecorder().let {
it.setAudioSource(MediaRecorder.AudioSource.DEFAULT) it.setAudioSource(MediaRecorder.AudioSource.DEFAULT)
setOutputFormat(it) setOutputFormat(it)
it.setAudioEncodingBitRate(24000) 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() { override fun startRecord() {
init() init()
outputFile = File(outputDirectory, "Voice message.$filenameExt") outputFile = File(outputDirectory, "Voice message.$filenameExt")