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 3b83364f35..44fc6afa4e 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 @@ -22,6 +22,7 @@ import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog @@ -95,6 +96,12 @@ fun logPermissionStatuses(context: Context) { } } +fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher> { + return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> + allGranted.invoke(result.keys.all { result[it] == true }) + } +} + /** * See [.checkPermissions] * @@ -106,21 +113,21 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int, activity: Activity, requestCode: Int, @StringRes rationaleMessage: Int = 0): Boolean { - return checkPermissions(permissionsToBeGrantedBitMap, activity, null, null, requestCode, rationaleMessage) + return checkPermissions(permissionsToBeGrantedBitMap, activity, null, requestCode, rationaleMessage) } /** * See [.checkPermissions] * * @param permissionsToBeGrantedBitMap - * @param fragment + * @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, - fragment: Fragment, - @StringRes rationaleMessage: Int = 0, - allGranted: (Boolean) -> Unit): Boolean { - return checkPermissions(permissionsToBeGrantedBitMap, fragment.activity, fragment, allGranted, 0, rationaleMessage) + activity: Activity, + activityResultLauncher: ActivityResultLauncher>, + @StringRes rationaleMessage: Int = 0): Boolean { + return checkPermissions(permissionsToBeGrantedBitMap, activity, activityResultLauncher, 0, rationaleMessage) } /** @@ -138,23 +145,19 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int, * * @param permissionsToBeGrantedBitMap the permissions bit map to be granted * @param activity the calling Activity that is requesting the permissions (or fragment parent) - * @param fragment the calling fragment that is requesting the permissions + * @param activityResultLauncher from the calling fragment that is requesting the permissions * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow) */ private fun checkPermissions(permissionsToBeGrantedBitMap: Int, - activity: Activity?, - fragment: Fragment?, - allGranted: ((Boolean) -> Unit)?, + activity: Activity, + activityResultLauncher: ActivityResultLauncher>?, requestCode: Int, @StringRes rationaleMessage: Int ): Boolean { var isPermissionGranted = false // sanity check - if (null == activity) { - Timber.w("## checkPermissions(): invalid input data") - isPermissionGranted = false - } else if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) { + if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) { isPermissionGranted = true } else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap && PERMISSIONS_FOR_VIDEO_IP_CALL != permissionsToBeGrantedBitMap @@ -224,9 +227,7 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int, .setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() } .setPositiveButton(R.string.ok) { _, _ -> if (permissionsListToBeGranted.isNotEmpty()) { - fragment?.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> - allGranted?.invoke(result.keys.all { result[it] == true }) - } + activityResultLauncher ?.launch(permissionsListToBeGranted.toTypedArray()) ?: run { ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode) @@ -267,9 +268,7 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int, .show() */ } else { - fragment?.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> - allGranted?.invoke(result.keys.all { result[it] == true }) - } + activityResultLauncher ?.launch(permissionsArrayToBeGranted) ?: run { ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode) 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 b176cc0815..a1ce84e2f4 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 @@ -28,6 +28,7 @@ import im.vector.app.core.extensions.configureWith 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.registerForPermissionsResult import im.vector.app.features.crypto.verification.VerificationAction import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel import im.vector.app.features.qrcode.QrCodeScannerActivity @@ -73,12 +74,14 @@ class VerificationChooseMethodFragment @Inject constructor( state.pendingRequest.invoke()?.transactionId ?: "")) } + private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + doOpenQRCodeScanner() + } + } + override fun openCamera() { - if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this) { allGranted -> - if (allGranted) { - doOpenQRCodeScanner() - } - }) { + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { doOpenQRCodeScanner() } } 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 6ee6212cf6..c861e5ce73 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 @@ -99,6 +99,7 @@ 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.openUrlInExternalBrowser +import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.shareMedia import im.vector.app.core.utils.toast @@ -782,40 +783,35 @@ class RoomDetailFragment @Inject constructor( } } + private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted -> + 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) + cleanUpAfterPermissionNotGranted() + } + } + + private fun safeStartCall2(isVideoCall: Boolean) { val startCallAction = RoomDetailAction.StartCall(isVideoCall) roomDetailViewModel.pendingAction = startCallAction if (isVideoCall) { if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, - this, - R.string.permissions_rationale_msg_camera_and_audio) { allGranted -> - 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) - cleanUpAfterPermissionNotGranted() - } - }) { + requireActivity(), + startCallActivityResultLauncher, + R.string.permissions_rationale_msg_camera_and_audio)) { roomDetailViewModel.pendingAction = null roomDetailViewModel.handle(startCallAction) } } else { if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, - this, - R.string.permissions_rationale_msg_record_audio) { allGranted -> - 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) - cleanUpAfterPermissionNotGranted() - } - }) { + requireActivity(), + startCallActivityResultLauncher, + R.string.permissions_rationale_msg_record_audio)) { roomDetailViewModel.pendingAction = null roomDetailViewModel.handle(startCallAction) } @@ -1005,6 +1001,18 @@ class RoomDetailFragment @Inject constructor( } } + private val writingFileActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + val pendingUri = roomDetailViewModel.pendingUri + if (pendingUri != null) { + roomDetailViewModel.pendingUri = null + sendUri(pendingUri) + } + } else { + cleanUpAfterPermissionNotGranted() + } + } + private fun setupComposer() { autoCompleter.setup(composerLayout.composerEditText) @@ -1037,17 +1045,7 @@ class RoomDetailFragment @Inject constructor( override fun onRichContentSelected(contentUri: Uri): Boolean { // We need WRITE_EXTERNAL permission - return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this@RoomDetailFragment) { allGranted -> - if (allGranted) { - val pendingUri = roomDetailViewModel.pendingUri - if (pendingUri != null) { - roomDetailViewModel.pendingUri = null - sendUri(pendingUri) - } - } else { - cleanUpAfterPermissionNotGranted() - } - }) { + return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), writingFileActivityResultLauncher)) { sendUri(contentUri) } else { roomDetailViewModel.pendingUri = contentUri @@ -1556,18 +1554,20 @@ class RoomDetailFragment @Inject constructor( ) } + private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + sharedActionViewModel.pendingAction?.let { + handleActions(it) + sharedActionViewModel.pendingAction = null + } + } else { + cleanUpAfterPermissionNotGranted() + } + } + private fun onSaveActionClicked(action: EventSharedAction.Save) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q - && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this) { allGranted -> - if (allGranted) { - sharedActionViewModel.pendingAction?.let { - handleActions(it) - sharedActionViewModel.pendingAction = null - } - } else { - cleanUpAfterPermissionNotGranted() - } - }) { + && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), saveActionActivityResultLauncher)) { sharedActionViewModel.pendingAction = action return } @@ -1805,18 +1805,20 @@ class RoomDetailFragment @Inject constructor( // AttachmentTypeSelectorView.Callback + private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + val pendingType = attachmentsHelper.pendingType + if (pendingType != null) { + attachmentsHelper.pendingType = null + launchAttachmentProcess(pendingType) + } + } else { + cleanUpAfterPermissionNotGranted() + } + } + override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) { - if (checkPermissions(type.permissionsBit, this) { allGranted -> - if (allGranted) { - val pendingType = attachmentsHelper.pendingType - if (pendingType != null) { - attachmentsHelper.pendingType = null - launchAttachmentProcess(pendingType) - } - } else { - cleanUpAfterPermissionNotGranted() - } - }) { + if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) { launchAttachmentProcess(type) } else { attachmentsHelper.pendingType = type diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index f5c80b378f..a6e1fda793 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -48,6 +48,7 @@ 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.copyToClipboard +import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.features.crypto.util.toImageRes import im.vector.app.features.home.AvatarRenderer @@ -283,14 +284,16 @@ class RoomProfileFragment @Inject constructor( .show() } + private val takePhotoActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + onAvatarTypeSelected(true) + } + } + private var avatarCameraUri: Uri? = null private fun onAvatarTypeSelected(isCamera: Boolean) { if (isCamera) { - if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this) { allGranted -> - if (allGranted) { - onAvatarTypeSelected(true) - } - }) { + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), takePhotoActivityResultLauncher)) { avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) } } else { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 2f2c0d4c33..c1d8eb1abe 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -51,6 +51,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.getSizeOfFiles +import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.toast import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs @@ -390,13 +391,15 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { }.show() } + private val takePhotoActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + onAvatarTypeSelected(true) + } + } + private fun onAvatarTypeSelected(isCamera: Boolean) { if (isCamera) { - if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this) { allGranted -> - if (allGranted) { - onAvatarTypeSelected(true) - } - }) { + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), takePhotoActivityResultLauncher)) { avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) } } else {