From e716290523e72a35af15b9ec33f873af4e8f3a38 Mon Sep 17 00:00:00 2001 From: Bohdan Shulyar Date: Wed, 13 Aug 2025 13:40:07 +0300 Subject: [PATCH] android: add runtime permissions for older android versions --- android/app/src/main/AndroidManifest.xml | 4 +- .../xash/engine/ui/library/LibraryFragment.kt | 101 +++++++++++++++--- android/app/src/main/res/values/strings.xml | 2 + 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f3f98b8b..25112818 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,10 +29,10 @@ + android:maxSdkVersion="29" /> + android:maxSdkVersion="29" /> diff --git a/android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt b/android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt index a43aa883..f1b2312b 100644 --- a/android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt +++ b/android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt @@ -1,6 +1,8 @@ package su.xash.engine.ui.library +import android.Manifest import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -13,7 +15,8 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -25,6 +28,7 @@ import su.xash.engine.R import su.xash.engine.adapters.GameAdapter import su.xash.engine.databinding.FragmentLibraryBinding + class LibraryFragment : Fragment(), MenuProvider { private var _binding: FragmentLibraryBinding? = null private val binding get() = _binding!! @@ -38,23 +42,78 @@ class LibraryFragment : Fragment(), MenuProvider { } } - private fun checkStoragePermissions(): Boolean { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { - MaterialAlertDialogBuilder(requireContext()).apply { - setTitle(R.string.file_access_required) - setMessage(R.string.file_access_message) - setPositiveButton(android.R.string.ok) { _, _ -> - startActivityForResult.launch( - Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData( - Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) - ) - ) - } - setCancelable(false) - show() - } + private val requiredPermissions = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) - return false + private val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + val granted = permissions.entries.all { it.value } + if (granted) { + libraryViewModel.reloadGames(requireContext()) + } else { + checkStoragePermissions() + } + } + + private fun checkStoragePermissions(): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.file_access_required) + setMessage(R.string.file_access_message) + setPositiveButton(android.R.string.ok) { _, _ -> + startActivityForResult.launch( + Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData( + Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) + ) + ) + } + setCancelable(false) + show() + + return false + } else { + return true + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val permissionsNeeded = requiredPermissions.filter { + ContextCompat.checkSelfPermission( + requireContext(), + it + ) != PackageManager.PERMISSION_GRANTED + }.toTypedArray() + + if (!permissionsNeeded.isEmpty()) { + val showRationale = permissionsNeeded.any { + ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), it) + } + + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.external_storage_required) + setMessage(R.string.external_storage_message) + setPositiveButton(android.R.string.ok) { _, _ -> + if (showRationale) { + requestPermissionLauncher.launch(permissionsNeeded) + } else { + val intent = + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = + Uri.fromParts("package", requireContext().packageName, null) + } + startActivity(intent) + } + } + setCancelable(false) + show() + } + + return false + } else { + return true + } } else { return true } @@ -107,4 +166,12 @@ class LibraryFragment : Fragment(), MenuProvider { return false } + + override fun onResume() { + super.onResume() + + if (checkStoragePermissions()) { + libraryViewModel.reloadGames(requireContext()) + } + } } diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 3d85f4d5..ff46f4ed 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -16,4 +16,6 @@ All-files access required All-files access is required for the app to function properly. Select current directory + External storage access required + External storage access is required for the app to function properly.