diff --git a/application/resources/assets/images/svg/ic_permission_check_unchecked.svg b/application/resources/assets/images/svg/ic_permission_check_unchecked.svg
new file mode 100644
index 000000000..f226d6a9a
--- /dev/null
+++ b/application/resources/assets/images/svg/ic_permission_check_unchecked.svg
@@ -0,0 +1,51 @@
+
+
+
+
diff --git a/application/resources/assets/images/svg/ic_permission_media_audio.svg b/application/resources/assets/images/svg/ic_permission_media_audio.svg
new file mode 100644
index 000000000..cf831ccb1
--- /dev/null
+++ b/application/resources/assets/images/svg/ic_permission_media_audio.svg
@@ -0,0 +1,244 @@
+
+
+
+
diff --git a/application/resources/assets/images/svg/ic_permission_media_video.svg b/application/resources/assets/images/svg/ic_permission_media_video.svg
new file mode 100644
index 000000000..042a3c91c
--- /dev/null
+++ b/application/resources/assets/images/svg/ic_permission_media_video.svg
@@ -0,0 +1,244 @@
+
+
+
+
diff --git a/application/resources/src/main/res/drawable/ic_permission_media_audio.xml b/application/resources/src/main/res/drawable/ic_permission_media_audio.xml
new file mode 100644
index 000000000..fe873f050
--- /dev/null
+++ b/application/resources/src/main/res/drawable/ic_permission_media_audio.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
diff --git a/application/resources/src/main/res/drawable/ic_permission_media_audio_denied.xml b/application/resources/src/main/res/drawable/ic_permission_media_audio_denied.xml
new file mode 100644
index 000000000..1fef7a5a5
--- /dev/null
+++ b/application/resources/src/main/res/drawable/ic_permission_media_audio_denied.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
diff --git a/application/resources/src/main/res/drawable/ic_permission_media_video.xml b/application/resources/src/main/res/drawable/ic_permission_media_video.xml
new file mode 100644
index 000000000..dd24e6434
--- /dev/null
+++ b/application/resources/src/main/res/drawable/ic_permission_media_video.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/application/resources/src/main/res/drawable/ic_permission_media_video_denied.xml b/application/resources/src/main/res/drawable/ic_permission_media_video_denied.xml
new file mode 100644
index 000000000..dd25a7b32
--- /dev/null
+++ b/application/resources/src/main/res/drawable/ic_permission_media_video_denied.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/application/resources/src/main/res/drawable/rounded_corners_permissions_explanation.xml b/application/resources/src/main/res/drawable/rounded_corners_permissions_explanation.xml
new file mode 100644
index 000000000..d31658884
--- /dev/null
+++ b/application/resources/src/main/res/drawable/rounded_corners_permissions_explanation.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/application/resources/src/main/res/values/attrs.xml b/application/resources/src/main/res/values/attrs.xml
index 82cae68c9..b5bbc69fb 100644
--- a/application/resources/src/main/res/values/attrs.xml
+++ b/application/resources/src/main/res/values/attrs.xml
@@ -81,6 +81,7 @@
+
diff --git a/application/resources/src/main/res/values/strings.xml b/application/resources/src/main/res/values/strings.xml
index a73d8959f..3cedfcf44 100644
--- a/application/resources/src/main/res/values/strings.xml
+++ b/application/resources/src/main/res/values/strings.xml
@@ -1113,6 +1113,14 @@
Notification permission
VLC needs your permission to send notifications.\nThe app will only notify you when scanning your media or using the custom video Picture in Picture.\nRefusing it won\'t prevent VLC to work, however the notifications will be hidden.
Permission
+ No file access
+ Permissions
+ List of all the permissions
+ File permissions
+ Access to standard media
+ All file access
+ To enable this, you have to disable the standard media file access first
+ To enable this, you have to disable the full file permission first
Customize
%d selected
Control settings
diff --git a/application/vlc-android/res/layout/dialog_permissions.xml b/application/vlc-android/res/layout/dialog_permissions.xml
new file mode 100644
index 000000000..ae232afe5
--- /dev/null
+++ b/application/vlc-android/res/layout/dialog_permissions.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/application/vlc-android/res/values/styles.xml b/application/vlc-android/res/values/styles.xml
index 35fa36452..349125fd2 100644
--- a/application/vlc-android/res/values/styles.xml
+++ b/application/vlc-android/res/values/styles.xml
@@ -77,6 +77,7 @@
- @color/orange_800_transparent_20
- ?attr/background_default
- @style/Widget.MaterialComponents.Button.TextButton.Dialog
+ - @color/grey200
- @drawable/gridview_progressbar
- @drawable/progress_mini_player
@@ -269,6 +270,7 @@
- @color/orange_500_transparent_20
- ?attr/background_default_darker
- @style/Widget.MaterialComponents.Button.TextButton.Dialog
+ - @color/grey800
- @drawable/gridview_progressbar_w
- @drawable/progress_mini_player_black
diff --git a/application/vlc-android/res/xml/preferences.xml b/application/vlc-android/res/xml/preferences.xml
index 9c38080d1..4610820de 100644
--- a/application/vlc-android/res/xml/preferences.xml
+++ b/application/vlc-android/res/xml/preferences.xml
@@ -50,6 +50,12 @@
android:summary="%s"
android:title="@string/metered_connection"/>
+
+
+
binding.manageAllPermsCheck.isChecked = true
+ Permissions.hasAnyFileFineAccess(requireActivity()) -> binding.manageMediaPermsCheck.isChecked = true
+ else -> binding.noAccessCheck.isChecked = true
+ }
+
+ // media permission states
+ binding.manageMediaVideo.isEnabled = !Permissions.hasAllAccess(requireActivity()) && !Permissions.hasVideoPermission(requireActivity())
+ binding.manageMediaAudio.isEnabled = !Permissions.hasAllAccess(requireActivity()) && !Permissions.hasAudioPermission(requireActivity())
+
+ //backgrounds
+ binding.manageMediaPermsCheck.setBackgroundResource(defaultBackground)
+
+
+ // explanation text state
+ binding.fileAccessExplanation.text = when {
+ Permissions.hasAllAccess(requireActivity()) -> getString(R.string.permission_onboarding_perm_all)
+ Permissions.hasAnyFileFineAccess(requireActivity()) -> getString(R.string.permission_onboarding_perm_media)
+ else -> getString(R.string.permission_expanation_no_allow)
+ }
+ binding.fileAccessExplanation.setCompoundDrawablesRelativeWithIntrinsicBounds(when {
+ Permissions.hasAllAccess(requireActivity()) -> ContextCompat.getDrawable(requireActivity(), R.drawable.ic_perm_all)
+ Permissions.hasAnyFileFineAccess(requireActivity()) -> ContextCompat.getDrawable(requireActivity(), R.drawable.ic_perm_media)
+ else -> ContextCompat.getDrawable(requireActivity(), R.drawable.ic_perm_none)
+ }, null, null, null)
+
+
+ //warning visibility
+ binding.allAccessWarning.setInvisible()
+ binding.allAccessWarning.text = when {
+ Permissions.hasAllAccess(requireActivity()) -> getString(R.string.permission_media_warning)
+ Permissions.hasAnyFileFineAccess(requireActivity()) -> getString(R.string.permission_all_warning)
+ else -> ""
+ }
+
+
+ //media permission icons
+ binding.manageMediaVideo.setImageDrawable(
+ ContextCompat.getDrawable(
+ requireActivity(),
+ if (Permissions.hasVideoPermission(requireActivity())) R.drawable.ic_permission_media_video else R.drawable.ic_permission_media_video_denied
+ )
+ )
+ binding.manageMediaAudio.setImageDrawable(
+ ContextCompat.getDrawable(
+ requireActivity(),
+ if (Permissions.hasAudioPermission(requireActivity())) R.drawable.ic_permission_media_audio else R.drawable.ic_permission_media_audio_denied
+ )
+ )
+
+ //click listeners
+ binding.noAccessCheck.setOnClickListener {
+ if (Permissions.hasAllAccess(requireActivity()) || Permissions.hasAnyFileFineAccess(
+ requireActivity()
+ )
+ ) {
+ (it as RadioButton).isChecked = false
+ when {
+ Permissions.hasAllAccess(requireActivity()) -> binding.manageAllPermsCheck
+ else -> binding.manageMediaPermsCheck
+ }.background = ContextCompat.getDrawable(requireActivity(), R.drawable.rounded_corners_permissions_warning)
+ showWarning()
+ }
+ }
+
+ binding.manageAllPermsCheck.setOnClickListener {
+ if (Permissions.hasAnyFileFineAccess(requireActivity())) {
+ (it as RadioButton).isChecked = false
+ binding.manageMediaPermsCheck.background = ContextCompat.getDrawable(requireActivity(), R.drawable.rounded_corners_permissions_warning)
+ showWarning()
+ } else
+ requireActivity().lifecycleScope.launch {
+ val uri = Uri.fromParts(SCHEME_PACKAGE, requireContext().packageName, null)
+ val intent = Intent(
+ android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
+ uri
+ )
+ startActivity(intent)
+ }
+ }
+
+ binding.manageMediaPermsCheck.setOnClickListener {
+ if (Permissions.hasAnyFileFineAccess(requireActivity())) {
+ Permissions.showAppSettingsPage(requireActivity())
+ (it as RadioButton).isChecked = false
+ } else if (Permissions.hasAllAccess(requireActivity())) {
+ (it as RadioButton).isChecked = false
+ binding.manageAllPermsCheck.background = ContextCompat.getDrawable(requireActivity(), R.drawable.rounded_corners_permissions_warning)
+ showWarning()
+ } else
+ ActivityCompat.requestPermissions(
+ requireActivity(), arrayOf(
+ Manifest.permission.READ_MEDIA_VIDEO,
+ Manifest.permission.READ_MEDIA_AUDIO
+ ), Permissions.FINE_STORAGE_PERMISSION_REQUEST_CODE
+ )
+ }
+
+ binding.manageMediaAudio.setOnClickListener {
+ if (!Permissions.hasAllAccess(requireActivity()) && !Permissions.hasAudioPermission(requireActivity())) {
+ ActivityCompat.requestPermissions(
+ requireActivity(), arrayOf(
+ Manifest.permission.READ_MEDIA_AUDIO
+ ), Permissions.FINE_STORAGE_PERMISSION_REQUEST_CODE
+ )
+ }
+ }
+
+ binding.manageMediaVideo.setOnClickListener {
+ if (!Permissions.hasAllAccess(requireActivity()) && !Permissions.hasVideoPermission(requireActivity())) {
+ ActivityCompat.requestPermissions(
+ requireActivity(), arrayOf(
+ Manifest.permission.READ_MEDIA_VIDEO
+ ), Permissions.FINE_STORAGE_PERMISSION_REQUEST_CODE
+ )
+ }
+ }
+
+
+ }
+
+ private fun showWarning() {
+ binding.allAccessWarning.translationY = 100.dp.toFloat()
+ binding.allAccessWarning.setVisible()
+ binding.allAccessWarning.animate().translationY(0F).setDuration(300).start()
+ }
+
+}
+
+
+
+
+
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesFragment.kt b/application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesFragment.kt
index 59b6b2969..e3c5b6a79 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesFragment.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesFragment.kt
@@ -57,7 +57,9 @@ import org.videolan.vlc.R
import org.videolan.vlc.gui.PinCodeActivity
import org.videolan.vlc.gui.PinCodeReason
import org.videolan.vlc.gui.SecondaryActivity
+import org.videolan.vlc.gui.dialogs.AboutVersionDialog
import org.videolan.vlc.gui.dialogs.ConfirmAudioPlayQueueDialog
+import org.videolan.vlc.gui.dialogs.PermissionListDialog
import org.videolan.vlc.gui.helpers.UiTools
import org.videolan.vlc.gui.preferences.search.PreferenceItem
import org.videolan.vlc.util.Permissions
@@ -147,6 +149,10 @@ class PreferencesFragment : BasePreferenceFragment(), SharedPreferences.OnShared
pinCodeResult.launch(intent)
}
}
+ "permissions" -> {
+ PermissionListDialog.newInstance().show(requireActivity().supportFragmentManager, "PermissionListDialog")
+
+ }
"remote_access_category" -> loadFragment(PreferencesRemoteAccess())
"android_auto_category" -> loadFragment(PreferencesAndroidAuto())
PLAYBACK_HISTORY -> {
diff --git a/application/vlc-android/src/org/videolan/vlc/util/Permissions.kt b/application/vlc-android/src/org/videolan/vlc/util/Permissions.kt
index 20f43c334..44b1cd13c 100644
--- a/application/vlc-android/src/org/videolan/vlc/util/Permissions.kt
+++ b/application/vlc-android/src/org/videolan/vlc/util/Permissions.kt
@@ -127,6 +127,36 @@ object Permissions {
}
+ fun hasAudioPermission(context: Context) = (
+ ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.READ_MEDIA_AUDIO
+ ) == PackageManager.PERMISSION_GRANTED
+ )
+ fun hasVideoPermission(context: Context) = (
+ ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.READ_MEDIA_VIDEO
+ ) == PackageManager.PERMISSION_GRANTED
+ )
+
+ fun hasAnyFileFineAccess(context: Context) = (
+ ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.READ_MEDIA_AUDIO
+ ) == PackageManager.PERMISSION_GRANTED
+ ||
+ ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.READ_MEDIA_VIDEO
+ ) == PackageManager.PERMISSION_GRANTED
+ ||
+ ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.READ_MEDIA_IMAGES
+ ) == PackageManager.PERMISSION_GRANTED
+ )
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun isAnyFileFinePermissionGranted(context: Context) = (
ContextCompat.checkSelfPermission(