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 index 3eca2b49be..e03565adfd 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt @@ -26,6 +26,8 @@ import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.PERMISSIONS_ALL 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 @@ -34,6 +36,8 @@ class DebugPermissionActivity : VectorBaseActivity() override fun initUiAndData() { @@ -67,12 +71,19 @@ class DebugPermissionActivity : VectorBaseActivity if (allGranted) { Toast.makeText(this, "All granted", Toast.LENGTH_SHORT).show() } else { if (deniedPermanently) { - Toast.makeText(this, "Denied forever", Toast.LENGTH_SHORT).show() + dialogOrSnackbar = !dialogOrSnackbar + if (dialogOrSnackbar) { + onPermissionDeniedDialog(R.string.denied_permission_camera) + } else { + onPermissionDeniedSnackbar(R.string.denied_permission_camera) + } } else { Toast.makeText(this, "Denied", Toast.LENGTH_SHORT).show() } 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/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index 3901e63eb0..edeedb9493 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 @@ -26,6 +26,7 @@ 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 @@ -78,9 +79,22 @@ fun ComponentActivity.registerForPermissionsResult(lambda: (allGranted: Boolean, } } -fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher> { +fun Fragment.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit): ActivityResultLauncher> { return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> - allGranted.invoke(result.keys.all { result[it] == true }) + 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) + } + } + // Reset + permissionDialogDisplayed = false } } @@ -163,3 +177,14 @@ fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage openAppSettingsPage(this) } } + +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/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 f7fd4026b0..b88a1a6e3a 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 @@ -101,6 +101,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 @@ -1062,14 +1063,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() } } @@ -1738,13 +1741,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() } } @@ -1977,7 +1983,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) { @@ -1985,6 +1991,9 @@ class RoomDetailFragment @Inject constructor( launchAttachmentProcess(pendingType) } } else { + if (deniedPermanently) { + activity?.onPermissionDeniedDialog(R.string.denied_permission_generic) + } cleanUpAfterPermissionNotGranted() } } 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..ed7043bb41 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 { 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/values/strings.xml b/vector/src/main/res/values/strings.xml index 0159c98c20..6c18eb2db5 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