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/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 @@
 
     <application>
         <activity android:name=".features.debug.TestLinkifyActivity" />
+        <activity android:name=".features.debug.DebugPermissionActivity" />
         <activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
     </application>
 
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<ActivityDebugMenuBinding>() {
         }
         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<ActivityDebugMenuBinding>() {
     }
 
     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<out String>, 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<ActivityDebugPermissionBinding>() {
+
+    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<String>()
+
+    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" />
 
+            <Button
+                android:id="@+id/debug_permission"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Permissions" />
+
         </LinearLayout>
 
     </ScrollView>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/coordinatorLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".features.debug.DebugPermissionActivity"
+    tools:ignore="HardcodedText">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:divider="@drawable/linear_divider"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            android:padding="@dimen/layout_horizontal_margin"
+            android:showDividers="middle">
+
+            <TextView
+                android:id="@+id/status"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                tools:text="Status" />
+
+            <Button
+                android:id="@+id/camera"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="CAMERA"
+                android:textAllCaps="false" />
+
+            <Button
+                android:id="@+id/audio"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="RECORD_AUDIO"
+                android:textAllCaps="false" />
+
+            <Button
+                android:id="@+id/camera_audio"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="CAMERA + RECORD_AUDIO"
+                android:textAllCaps="false" />
+
+            <Button
+                android:id="@+id/write"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="WRITE_EXTERNAL_STORAGE"
+                android:textAllCaps="false" />
+
+            <Button
+                android:id="@+id/read"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="READ_EXTERNAL_STORAGE"
+                android:textAllCaps="false" />
+
+            <Button
+                android:id="@+id/contact"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="READ_CONTACTS"
+                android:textAllCaps="false" />
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="debug_rationale">Rationale!</string>
+</resources>
\ 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<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
+abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), HasScreenInjector {
     /* ==========================================================================================
      * View
      * ========================================================================================== */
@@ -596,12 +595,19 @@ abstract class VectorBaseActivity<VB: ViewBinding> : 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<String>()
 
-// 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<Array<String>> {
+    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<Array<String>> {
+    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<String, Boolean>, 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<Array<String>> {
-    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<Array<String>>,
-                     @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<Array<String>>?,
-                             requestCode: Int,
-                             @StringRes rationaleMessage: Int
-): Boolean {
-    var isPermissionGranted = false
+fun checkPermissions(permissionsToBeGranted: List<String>,
+                     activity: Activity,
+                     activityResultLauncher: ActivityResultLauncher<Array<String>>,
+                     @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<String>()
-        val permissionsListToBeGranted = ArrayList<String>()
-        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<String>,
+                                         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<String>,
-                                         permissionsListToBeGrantedOut: MutableList<String>,
-                                         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<String>) {
         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<ActivityCallBinding>(), 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<ActivityCallBinding>(), CallContro
         }
     }
 
-    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, 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<ActivityCallBinding>(), 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<String>, 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 c4caff025b..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,12 +1991,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/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<String>, 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<ActivitySimpleBinding>(),
 
         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 @@
     <string name="reset">Reset</string>
     <string name="start_chatting">Start Chatting</string>
 
+    <!-- Permissions denied forever -->
+    <string name="denied_permission_generic">Some permissions are missing to perform this action, please grant the permissions from the system settings.</string>
+    <string name="denied_permission_camera">To perform this action, please grant the Camera permission from the system settings.</string>
 
     <!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video -->
     <string name="ongoing_conference_call">Ongoing conference call.\nJoin as %1$s or %2$s</string>
     <string name="ongoing_conference_call_voice">Voice</string>
     <string name="ongoing_conference_call_video">Video</string>
     <string name="cannot_start_call">Cannot start the call, please try later</string>
+    <string name="missing_permissions_title">Missing permissions</string>
     <string name="missing_permissions_warning">"Due to missing permissions, some features may be missing…</string>
     <string name="missing_permissions_error">"Due to missing permissions, this action is not possible.</string>
     <string name="missing_permissions_to_start_conf_call">You need permission to invite to start a conference in this room</string>