diff --git a/changelog.d/3655.bugfix b/changelog.d/3655.bugfix
new file mode 100644
index 0000000000..c7e805ab84
--- /dev/null
+++ b/changelog.d/3655.bugfix
@@ -0,0 +1 @@
+Fix unread messages marker being hidden in collapsed membership item
diff --git a/changelog.d/3661.bugfix b/changelog.d/3661.bugfix
new file mode 100644
index 0000000000..9ca6b7d188
--- /dev/null
+++ b/changelog.d/3661.bugfix
@@ -0,0 +1 @@
+Ensure reaction emoji picker tabs look fine on small displays
diff --git a/changelog.d/3667.feature b/changelog.d/3667.feature
new file mode 100644
index 0000000000..439a890dd0
--- /dev/null
+++ b/changelog.d/3667.feature
@@ -0,0 +1 @@
+Better management of permission requests
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
index f8a8354e48..1d40e5defd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
@@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
- // Sign userSigningKey with master
+ // Sign selfSigningKey with master
val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
.key(sskPublicKey)
.build()
diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml
index b97384099f..8ffcec6bc1 100644
--- a/vector/src/debug/AndroidManifest.xml
+++ b/vector/src/debug/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
index 539091c7ce..4b5228d199 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
@@ -30,9 +30,8 @@ import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
@@ -48,7 +47,6 @@ import im.vector.lib.ui.styles.debug.DebugVectorButtonStylesLightActivity
import im.vector.lib.ui.styles.debug.DebugVectorTextViewDarkActivity
import im.vector.lib.ui.styles.debug.DebugVectorTextViewLightActivity
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
-
import timber.log.Timber
import javax.inject.Inject
@@ -115,6 +113,9 @@ class DebugMenuActivity : VectorBaseActivity() {
}
views.debugTestCrash.setOnClickListener { testCrash() }
views.debugScanQrCode.setOnClickListener { scanQRCode() }
+ views.debugPermission.setOnClickListener {
+ startActivity(Intent(this, DebugPermissionActivity::class.java))
+ }
}
private fun renderQrCode(text: String) {
@@ -217,15 +218,13 @@ class DebugMenuActivity : VectorBaseActivity() {
}
private fun scanQRCode() {
- if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
+ if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
doScanQRCode()
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
-
- if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
+ private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
+ if (allGranted) {
doScanQRCode()
}
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt
new file mode 100644
index 0000000000..048c64bc3a
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 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.app.features.debug
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.os.Build
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import im.vector.app.R
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
+import im.vector.app.core.utils.onPermissionDeniedSnackbar
+import im.vector.app.core.utils.registerForPermissionsResult
+import im.vector.app.databinding.ActivityDebugPermissionBinding
+import timber.log.Timber
+
+class DebugPermissionActivity : VectorBaseActivity() {
+
+ override fun getBinding() = ActivityDebugPermissionBinding.inflate(layoutInflater)
+
+ override fun getCoordinatorLayout() = views.coordinatorLayout
+
+ // For debug
+ private val allPermissions = listOf(
+ Manifest.permission.CAMERA,
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.READ_CONTACTS)
+
+ private var lastPermissions = emptyList()
+
+ override fun initUiAndData() {
+ views.status.setOnClickListener { refresh() }
+
+ views.camera.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.CAMERA)
+ checkPerm()
+ }
+ views.audio.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.RECORD_AUDIO)
+ checkPerm()
+ }
+ views.cameraAudio.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+ checkPerm()
+ }
+ views.write.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ checkPerm()
+ }
+ views.read.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
+ checkPerm()
+ }
+ views.contact.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.READ_CONTACTS)
+ checkPerm()
+ }
+ }
+
+ private fun checkPerm() {
+ if (checkPermissions(lastPermissions, this, launcher, R.string.debug_rationale)) {
+ Toast.makeText(this, "Already granted, sync call", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private var dialogOrSnackbar = false
+
+ private val launcher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ Toast.makeText(this, "All granted", Toast.LENGTH_SHORT).show()
+ } else {
+ if (deniedPermanently) {
+ dialogOrSnackbar = !dialogOrSnackbar
+ if (dialogOrSnackbar) {
+ onPermissionDeniedDialog(R.string.denied_permission_generic)
+ } else {
+ onPermissionDeniedSnackbar(R.string.denied_permission_generic)
+ }
+ } else {
+ Toast.makeText(this, "Denied", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ refresh()
+ }
+
+ private fun refresh() {
+ views.status.text = getStatus()
+ }
+
+ private fun getStatus(): String {
+ return buildString {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Timber.v("## debugPermission() : log the permissions status used by the app")
+ allPermissions.forEach { permission ->
+ append("[$permission] : ")
+ if (ContextCompat.checkSelfPermission(this@DebugPermissionActivity, permission) == PackageManager.PERMISSION_GRANTED) {
+ append("PERMISSION_GRANTED")
+ } else {
+ append("PERMISSION_DENIED")
+ }
+ append(" show rational: ")
+ append(ActivityCompat.shouldShowRequestPermissionRationale(this@DebugPermissionActivity, permission))
+ append("\n")
+ }
+ } else {
+ append("Before M!")
+ }
+ append("\n")
+ append("(Click to refresh)")
+ }
+ }
+}
diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml
index a83f61266a..fadffecf83 100644
--- a/vector/src/debug/res/layout/activity_debug_menu.xml
+++ b/vector/src/debug/res/layout/activity_debug_menu.xml
@@ -152,6 +152,12 @@
android:layout_height="200dp"
tools:src="@drawable/ic_qr_code_add" />
+
+
diff --git a/vector/src/debug/res/layout/activity_debug_permission.xml b/vector/src/debug/res/layout/activity_debug_permission.xml
new file mode 100644
index 0000000000..6340d8faa7
--- /dev/null
+++ b/vector/src/debug/res/layout/activity_debug_permission.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/debug/res/values/strings.xml b/vector/src/debug/res/values/strings.xml
new file mode 100644
index 0000000000..a7b8e38634
--- /dev/null
+++ b/vector/src/debug/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Rationale!
+
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt
index 3b25fd3f89..23c2e13f6f 100644
--- a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt
+++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt
@@ -29,6 +29,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.lib.multipicker.MultiPicker
@@ -55,9 +56,11 @@ class GalleryOrCameraDialogHelper(
private val listener = fragment as? Listener ?: error("Fragment must implement GalleryOrCameraDialogHelper.Listener")
- private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted ->
+ private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
doOpenCamera()
+ } else if (deniedPermanently) {
+ activity.onPermissionDeniedDialog(R.string.denied_permission_camera)
}
}
@@ -116,7 +119,7 @@ class GalleryOrCameraDialogHelper(
private fun onAvatarTypeSelected(type: Type) {
when (type) {
- Type.Camera ->
+ Type.Camera ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) {
doOpenCamera()
}
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 899a99c314..61abbd445b 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -32,7 +32,6 @@ import androidx.annotation.MainThread
import androidx.annotation.MenuRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
-import com.google.android.material.appbar.MaterialToolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
@@ -42,6 +41,7 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.util.Util
+import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding3.view.clicks
import im.vector.app.BuildConfig
@@ -82,14 +82,13 @@ import im.vector.app.receivers.DebugReceiver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
-
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber
import java.util.concurrent.TimeUnit
import kotlin.system.measureTimeMillis
-abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
+abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
/* ==========================================================================================
* View
* ========================================================================================== */
@@ -596,12 +595,19 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScr
}
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
- getCoordinatorLayout()?.let {
- Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply {
+ val coordinatorLayout = getCoordinatorLayout()
+ if (coordinatorLayout != null) {
+ Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).apply {
withActionTitle?.let {
- setAction(withActionTitle, { action?.invoke() })
+ setAction(withActionTitle) { action?.invoke() }
}
}.show()
+ } else {
+ if (vectorPreferences.failFast()) {
+ error("No CoordinatorLayout to display this snackbar!")
+ } else {
+ Timber.w("No CoordinatorLayout to display this snackbar!")
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
index b6566b4ce9..4268a034f5 100644
--- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
@@ -18,117 +18,67 @@ package im.vector.app.core.utils
import android.Manifest
import android.app.Activity
-import android.content.Context
import android.content.pm.PackageManager
-import android.os.Build
-import android.widget.Toast
+import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity
-import timber.log.Timber
-
-// Android M permission request code management
-private const val PERMISSIONS_GRANTED = true
-private const val PERMISSIONS_DENIED = !PERMISSIONS_GRANTED
-
-// Permission bit
-private const val PERMISSION_BYPASSED = 0x0
-const val PERMISSION_CAMERA = 0x1
-private const val PERMISSION_WRITE_EXTERNAL_STORAGE = 0x1 shl 1
-private const val PERMISSION_RECORD_AUDIO = 0x1 shl 2
-private const val PERMISSION_READ_CONTACTS = 0x1 shl 3
-private const val PERMISSION_READ_EXTERNAL_STORAGE = 0x1 shl 4
// Permissions sets
-const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
-const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
-const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA
-const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
-const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
-const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
-const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
-const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
-const val PERMISSIONS_FOR_READING_FILES = PERMISSION_READ_EXTERNAL_STORAGE
-const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
+val PERMISSIONS_FOR_AUDIO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO)
+val PERMISSIONS_FOR_VIDEO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
+val PERMISSIONS_FOR_TAKING_PHOTO = listOf(Manifest.permission.CAMERA)
+val PERMISSIONS_FOR_MEMBERS_SEARCH = listOf(Manifest.permission.READ_CONTACTS)
+val PERMISSIONS_FOR_ROOM_AVATAR = listOf(Manifest.permission.CAMERA)
+val PERMISSIONS_FOR_WRITING_FILES = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+val PERMISSIONS_FOR_PICKING_CONTACT = listOf(Manifest.permission.READ_CONTACTS)
-const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
+val PERMISSIONS_EMPTY = emptyList()
-// Request code to ask permission to the system (arbitrary values)
-const val PERMISSION_REQUEST_CODE = 567
-const val PERMISSION_REQUEST_CODE_LAUNCH_CAMERA = 568
-const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA = 569
-const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA = 570
-const val PERMISSION_REQUEST_CODE_AUDIO_CALL = 571
-const val PERMISSION_REQUEST_CODE_VIDEO_CALL = 572
-const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
-const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
-const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
-const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
-const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579
+// This is not ideal to store the value like that, but it works
+private var permissionDialogDisplayed = false
/**
- * Log the used permissions statuses.
+ * First boolean is true if all permissions have been granted
+ * Second boolean is true if the permission is denied forever AND the permission request has not been displayed.
+ * So when the user does not grant the permission and check the box do not ask again, this boolean will be false.
+ * Only useful if the first boolean is false
*/
-fun logPermissionStatuses(context: Context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- val permissions = listOf(
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.READ_CONTACTS)
+fun ComponentActivity.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit)
+ : ActivityResultLauncher> {
+ return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
+ onPermissionResult(result, lambda)
+ }
+}
- Timber.v("## logPermissionStatuses() : log the permissions status used by the app")
+fun Fragment.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit): ActivityResultLauncher> {
+ return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
+ onPermissionResult(result, lambda)
+ }
+}
- for (permission in permissions) {
- Timber.v(("Status of [$permission] : " +
- if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission)) {
- "PERMISSION_GRANTED"
- } else {
- "PERMISSION_DENIED"
- }))
+private fun onPermissionResult(result: Map, lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit) {
+ if (result.keys.all { result[it] == true }) {
+ lambda(true, /* not used */ false)
+ } else {
+ if (permissionDialogDisplayed) {
+ // A permission dialog has been displayed, so even if the user has checked the do not ask again button, we do
+ // not tell the user to open the app settings
+ lambda(false, false)
+ } else {
+ // No dialog has been displayed, so tell the user to go to the system setting
+ lambda(false, true)
}
}
-}
-
-fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher> {
- return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
- allGranted.invoke(result.keys.all { result[it] == true })
- }
-}
-
-/**
- * See [.checkPermissions]
- *
- * @param permissionsToBeGrantedBitMap
- * @param activity
- * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
- */
-fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- activity: Activity,
- requestCode: Int,
- @StringRes rationaleMessage: Int = 0): Boolean {
- return checkPermissions(permissionsToBeGrantedBitMap, activity, null, requestCode, rationaleMessage)
-}
-
-/**
- * See [.checkPermissions]
- *
- * @param permissionsToBeGrantedBitMap
- * @param activityResultLauncher from the calling fragment that is requesting the permissions
- * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
- */
-fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- activity: Activity,
- activityResultLauncher: ActivityResultLauncher>,
- @StringRes rationaleMessage: Int = 0): Boolean {
- return checkPermissions(permissionsToBeGrantedBitMap, activity, activityResultLauncher, 0, rationaleMessage)
+ // Reset
+ permissionDialogDisplayed = false
}
/**
@@ -144,145 +94,65 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
* If a permission was already denied by the user, a popup is displayed to
* explain why vector needs the corresponding permission.
*
- * @param permissionsToBeGrantedBitMap the permissions bit map to be granted
- * @param activity the calling Activity that is requesting the permissions (or fragment parent)
- * @param activityResultLauncher from the calling fragment that is requesting the permissions
+ * @param permissionsToBeGranted the permissions to be granted
+ * @param activity the calling Activity that is requesting the permissions (or fragment parent)
+ * @param activityResultLauncher from the calling fragment/Activity that is requesting the permissions
+ * @param rationaleMessage message to be displayed BEFORE requesting for the permission
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/
-private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- activity: Activity,
- activityResultLauncher: ActivityResultLauncher>?,
- requestCode: Int,
- @StringRes rationaleMessage: Int
-): Boolean {
- var isPermissionGranted = false
+fun checkPermissions(permissionsToBeGranted: List,
+ activity: Activity,
+ activityResultLauncher: ActivityResultLauncher>,
+ @StringRes rationaleMessage: Int = 0): Boolean {
+ // retrieve the permissions to be granted according to the permission list
+ val missingPermissions = permissionsToBeGranted.filter { permission ->
+ ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_DENIED
+ }
- // sanity check
- if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
- isPermissionGranted = true
- } else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_VIDEO_IP_CALL != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_TAKING_PHOTO != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_MEMBERS_SEARCH != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_READING_FILES != permissionsToBeGrantedBitMap) {
- Timber.w("## checkPermissions(): permissions to be granted are not supported")
- isPermissionGranted = false
- } else {
- val permissionListAlreadyDenied = ArrayList()
- val permissionsListToBeGranted = ArrayList()
- var isRequestPermissionRequired = false
+ return if (missingPermissions.isNotEmpty()) {
+ permissionDialogDisplayed = !permissionsDeniedPermanently(missingPermissions, activity)
- // retrieve the permissions to be granted according to the request code bit map
- if (PERMISSION_CAMERA == permissionsToBeGrantedBitMap and PERMISSION_CAMERA) {
- val permissionType = Manifest.permission.CAMERA
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- if (PERMISSION_RECORD_AUDIO == permissionsToBeGrantedBitMap and PERMISSION_RECORD_AUDIO) {
- val permissionType = Manifest.permission.RECORD_AUDIO
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- if (PERMISSION_WRITE_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_WRITE_EXTERNAL_STORAGE) {
- val permissionType = Manifest.permission.WRITE_EXTERNAL_STORAGE
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- if (PERMISSION_READ_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_READ_EXTERNAL_STORAGE) {
- val permissionType = Manifest.permission.READ_EXTERNAL_STORAGE
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- // the contact book access is requested for any android platforms
- // for android M, we use the system preferences
- // for android < M, we use a dedicated settings
- if (PERMISSION_READ_CONTACTS == permissionsToBeGrantedBitMap and PERMISSION_READ_CONTACTS) {
- val permissionType = Manifest.permission.READ_CONTACTS
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- } else {
- // TODO uncomment
- /*if (!ContactsManager.getInstance().isContactBookAccessRequested) {
- isRequestPermissionRequired = true
- permissionsListToBeGranted.add(permissionType)
- }*/
- }
- }
-
- // if some permissions were already denied: display a dialog to the user before asking again.
- if (permissionListAlreadyDenied.isNotEmpty() && rationaleMessage != 0) {
- // display the dialog with the info text
+ if (rationaleMessage != 0 && permissionDialogDisplayed) {
+ // display the dialog with the info text. Do not do it if no system dialog will
+ // be displayed
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.permissions_rationale_popup_title)
.setMessage(rationaleMessage)
- .setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
+ .setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ ->
- if (permissionsListToBeGranted.isNotEmpty()) {
- activityResultLauncher
- ?.launch(permissionsListToBeGranted.toTypedArray())
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
- }
- }
+ activityResultLauncher.launch(missingPermissions.toTypedArray())
}
.show()
} else {
// some permissions are not granted, ask permissions
- if (isRequestPermissionRequired) {
- val permissionsArrayToBeGranted = permissionsListToBeGranted.toTypedArray()
-
- // for android < M, we use a custom dialog to request the contacts book access.
- if (permissionsListToBeGranted.contains(Manifest.permission.READ_CONTACTS)
- && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- TODO()
- /*
- MaterialAlertDialogBuilder(activity)
- .setIcon(android.R.drawable.ic_dialog_info)
- .setTitle(R.string.permissions_rationale_popup_title)
- .setMessage(R.string.permissions_msg_contacts_warning_other_androids)
- // gives the contacts book access
- .setPositiveButton(R.string.yes) { _, _ ->
- ContactsManager.getInstance().setIsContactBookAccessAllowed(true)
- fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
- }
- }
- // or reject it
- .setNegativeButton(R.string.no) { _, _ ->
- ContactsManager.getInstance().setIsContactBookAccessAllowed(false)
- fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
- }
- }
- .show()
- */
- } else {
- activityResultLauncher
- ?.launch(permissionsArrayToBeGranted)
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
- }
- }
- } else {
- // permissions were granted, start now.
- isPermissionGranted = true
- }
+ activityResultLauncher.launch(missingPermissions.toTypedArray())
}
+ false
+ } else {
+ // permissions were granted, start now.
+ true
}
+}
- return isPermissionGranted
+/**
+ * To be call after the permission request
+ *
+ * @param permissionsToBeGranted the permissions to be granted
+ * @param activity the calling Activity that is requesting the permissions (or fragment parent)
+ *
+ * @return true if one of the permission has been denied and the user check the do not ask again checkbox
+ */
+private fun permissionsDeniedPermanently(permissionsToBeGranted: List,
+ activity: Activity): Boolean {
+ return permissionsToBeGranted
+ .filter { permission ->
+ ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_DENIED
+ }
+ .any { permission ->
+ // If shouldShowRequestPermissionRationale() returns true, it means that the user as denied the permission, but not permanently.
+ // If it return false, it mean that the user as denied permanently the permission
+ ActivityCompat.shouldShowRequestPermissionRationale(activity, permission).not()
+ }
}
fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) {
@@ -291,50 +161,13 @@ fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage
}
}
-/**
- * Helper method used in [.checkPermissions] to populate the list of the
- * permissions to be granted (permissionsListToBeGrantedOut) and the list of the permissions already denied (permissionAlreadyDeniedListOut).
- *
- * @param activity calling activity
- * @param permissionAlreadyDeniedListOut list to be updated with the permissions already denied by the user
- * @param permissionsListToBeGrantedOut list to be updated with the permissions to be granted
- * @param permissionType the permission to be checked
- * @return true if the permission requires to be granted, false otherwise
- */
-private fun updatePermissionsToBeGranted(activity: Activity,
- permissionAlreadyDeniedListOut: MutableList,
- permissionsListToBeGrantedOut: MutableList,
- permissionType: String): Boolean {
- var isRequestPermissionRequested = false
-
- // add permission to be granted
- permissionsListToBeGrantedOut.add(permissionType)
-
- if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(activity.applicationContext, permissionType)) {
- isRequestPermissionRequested = true
-
- // add permission to the ones that were already asked to the user
- if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permissionType)) {
- permissionAlreadyDeniedListOut.add(permissionType)
- }
- }
- return isRequestPermissionRequested
-}
-
-/**
- * Return true if all permissions are granted, false if not or if permission request has been cancelled
- */
-fun allGranted(grantResults: IntArray): Boolean {
- if (grantResults.isEmpty()) {
- // A cancellation occurred
- return false
- }
-
- var granted = true
-
- grantResults.forEach {
- granted = granted && PackageManager.PERMISSION_GRANTED == it
- }
-
- return granted
+fun FragmentActivity.onPermissionDeniedDialog(@StringRes rationaleMessage: Int) {
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.missing_permissions_title)
+ .setMessage(rationaleMessage)
+ .setPositiveButton(R.string.open_settings) { _, _ ->
+ openAppSettingsPage(this)
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
}
diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
index cf7270225d..c0d4669108 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
@@ -206,7 +206,7 @@ class AttachmentTypeSelectorView(context: Context,
/**
* The all possible types to pick with their required permissions.
*/
- enum class Type(val permissionsBit: Int) {
+ enum class Type(val permissions: List) {
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
GALLERY(PERMISSIONS_EMPTY),
FILE(PERMISSIONS_EMPTY),
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
index 21939bd42b..7e84811102 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
@@ -40,8 +40,8 @@ import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.ActivityCallBinding
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
import im.vector.app.features.call.dialpad.DialPadFragment
@@ -139,11 +139,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
.disposeOnDestroy()
if (callArgs.isVideoCall) {
- if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, CAPTURE_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_camera_and_audio)) {
+ if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_camera_and_audio)) {
start()
}
} else {
- if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, this, CAPTURE_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_record_audio)) {
+ if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_record_audio)) {
start()
}
}
@@ -298,9 +298,8 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) {
+ private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
+ if (allGranted) {
start()
} else {
// TODO display something
@@ -370,8 +369,6 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
companion object {
-
- private const val CAPTURE_PERMISSION_REQUEST_CODE = 1
private const val EXTRA_MODE = "EXTRA_MODE"
private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG"
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 4aa5f023c4..68123d5e82 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -38,11 +38,9 @@ import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedSnackbar
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.contactsbook.ContactsBookFragment
import im.vector.app.features.contactsbook.ContactsBookViewModel
import im.vector.app.features.contactsbook.ContactsBookViewState
@@ -52,7 +50,6 @@ import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel
import im.vector.app.features.userdirectory.UserListViewState
-
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import java.net.HttpURLConnection
@@ -111,35 +108,31 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
}
private fun openAddByQrCode() {
- if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA, 0)) {
+ if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
}
}
private fun openPhoneBook() {
// Check permission first
- if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
- this,
- PERMISSION_REQUEST_CODE_READ_CONTACTS,
- 0)) {
+ if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, this, permissionReadContactLauncher)) {
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (allGranted(grantResults)) {
- if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
- doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
- } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
- addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
- }
- } else {
- if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
- onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
- } else if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
- onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
- }
+ private val permissionReadContactLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
+ } else if (deniedPermanently) {
+ onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
+ }
+ }
+
+ private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
+ } else if (deniedPermanently) {
+ onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
index 92a03c5483..8da0147a43 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
@@ -27,6 +27,7 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentQrCodeScannerBinding
import im.vector.app.features.userdirectory.PendingSelection
@@ -44,9 +45,11 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
}
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
startCamera()
+ } else if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
index 5d114b26bf..d3f24816a5 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
@@ -23,12 +23,14 @@ import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
+import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
@@ -79,9 +81,11 @@ class VerificationChooseMethodFragment @Inject constructor(
state.pendingRequest.invoke()?.transactionId ?: ""))
}
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
doOpenQRCodeScanner()
+ } else if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index 8b29339a0b..8307c7bbed 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
@@ -103,6 +103,7 @@ import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.core.utils.createUIHandler
import im.vector.app.core.utils.isValidUrl
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.openUrlInExternalBrowser
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.saveMedia
@@ -1107,14 +1108,16 @@ class RoomDetailFragment @Inject constructor(
}
}
- private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(it)
}
} else {
- context?.toast(R.string.permissions_action_not_performed_missing_permissions)
+ if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
+ }
cleanUpAfterPermissionNotGranted()
}
}
@@ -1800,13 +1803,16 @@ class RoomDetailFragment @Inject constructor(
}
}
- private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
sharedActionViewModel.pendingAction?.let {
handleActions(it)
sharedActionViewModel.pendingAction = null
}
} else {
+ if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
+ }
cleanUpAfterPermissionNotGranted()
}
}
@@ -2039,7 +2045,7 @@ class RoomDetailFragment @Inject constructor(
// AttachmentTypeSelectorView.Callback
- private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
val pendingType = attachmentsHelper.pendingType
if (pendingType != null) {
@@ -2047,12 +2053,15 @@ class RoomDetailFragment @Inject constructor(
launchAttachmentProcess(pendingType)
}
} else {
+ if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
+ }
cleanUpAfterPermissionNotGranted()
}
}
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
- if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) {
+ if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) {
launchAttachmentProcess(type)
} else {
attachmentsHelper.pendingType = type
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt
index 389dd15413..3121f031e2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt
@@ -73,7 +73,8 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut
}
epoxyModel.getEventIds().forEach { eventId ->
adapterPositionMapping[eventId] = index
- appendReadMarker = epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker
+ appendReadMarker = appendReadMarker
+ || (epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker)
}
}
if (epoxyModel is DaySeparatorItem) {
diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
index 142498e031..88998861bc 100644
--- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
@@ -21,7 +21,6 @@ import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.View
-import android.widget.Toast
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -33,9 +32,9 @@ import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedSnackbar
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.toast
import im.vector.app.features.contactsbook.ContactsBookFragment
import im.vector.app.features.contactsbook.ContactsBookViewModel
@@ -118,22 +117,16 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa
private fun openPhoneBook() {
// Check permission first
- if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
- this,
- PERMISSION_REQUEST_CODE_READ_CONTACTS,
- 0)) {
+ if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, this, permissionContactLauncher)) {
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (allGranted(grantResults)) {
- if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
- doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
- }
- } else {
- Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
+ private val permissionContactLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
+ } else if (deniedPermanently) {
+ onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt
index 2d03c7c4ca..da9c6792ff 100644
--- a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt
@@ -65,7 +65,7 @@ class ScanUserCodeFragment @Inject constructor()
}
}
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, _ ->
if (allGranted) {
startCamera()
} else {
@@ -112,7 +112,7 @@ class ScanUserCodeFragment @Inject constructor()
super.onResume()
// Register ourselves as a handler for scan results.
views.userCodeScannerView.setResultHandler(this)
- if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)) {
+ if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
startCamera()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt
index 042681d780..c451118813 100644
--- a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt
@@ -43,11 +43,11 @@ class ShowUserCodeFragment @Inject constructor(
val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
doOpenQRCodeScanner()
} else {
- sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted)
+ sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted(deniedPermanently))
}
}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt
index 3411fe3d7f..25a7bab7da 100644
--- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt
@@ -24,6 +24,6 @@ sealed class UserCodeActions : VectorViewModelAction {
data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions()
data class DecodedQRCode(val code: String) : UserCodeActions()
data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions()
- object CameraPermissionNotGranted : UserCodeActions()
+ data class CameraPermissionNotGranted(val deniedPermanently: Boolean) : UserCodeActions()
object ShareByText : UserCodeActions()
}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
index 6cdde6c880..0771a5d238 100644
--- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
@@ -81,13 +81,17 @@ class UserCodeActivity : VectorBaseActivity(),
sharedViewModel.observeViewEvents {
when (it) {
- UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
- UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true
- UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false
- is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
- is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
- UserCodeShareViewEvents.CameraPermissionNotGranted -> onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
- else -> {
+ UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
+ UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true
+ UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false
+ is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
+ is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
+ is UserCodeShareViewEvents.CameraPermissionNotGranted -> {
+ if (it.deniedPermanently) {
+ onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
+ }
+ }
+ else -> {
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt
index 67a1ab8a6c..eaa3b46af1 100644
--- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt
@@ -24,6 +24,6 @@ sealed class UserCodeShareViewEvents : VectorViewEvents {
object HideWaitingScreen : UserCodeShareViewEvents()
data class ToastMessage(val message: String) : UserCodeShareViewEvents()
data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents()
- object CameraPermissionNotGranted : UserCodeShareViewEvents()
+ data class CameraPermissionNotGranted(val deniedPermanently: Boolean) : UserCodeShareViewEvents()
data class SharePlainText(val text: String, val title: String, val richPlainText: String) : UserCodeShareViewEvents()
}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
index 9637b72581..071044fc8a 100644
--- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
@@ -76,7 +76,7 @@ class UserCodeSharedViewModel @AssistedInject constructor(
is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) }
is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
- UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted)
+ is UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted(action.deniedPermanently))
UserCodeActions.ShareByText -> handleShareByText()
}
}
diff --git a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
index 3e8f604d74..2e95fe7f7e 100644
--- a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
+++ b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
@@ -39,8 +39,10 @@
+ android:layout_height="40dp"
+ app:tabPaddingEnd="0dp"
+ app:tabPaddingStart="0dp" />
-
\ No newline at end of file
+
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 266c8aac01..37874b189b 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -386,12 +386,16 @@
Reset
Start Chatting
+
+ Some permissions are missing to perform this action, please grant the permissions from the system settings.
+ To perform this action, please grant the Camera permission from the system settings.
Ongoing conference call.\nJoin as %1$s or %2$s
Voice
Video
Cannot start the call, please try later
+ Missing permissions
"Due to missing permissions, some features may be missing…
"Due to missing permissions, this action is not possible.
You need permission to invite to start a conference in this room