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(