diff --git a/app/src/main/java/com/aurora/store/util/ApkCopier.kt b/app/src/main/java/com/aurora/store/util/ApkCopier.kt index c8d90934e..d6ab20e69 100644 --- a/app/src/main/java/com/aurora/store/util/ApkCopier.kt +++ b/app/src/main/java/com/aurora/store/util/ApkCopier.kt @@ -31,7 +31,7 @@ import java.util.zip.ZipOutputStream class ApkCopier(private val context: Context, private val packageName: String) { fun copy() { - val destination = File(PathUtil.getBaseCopyDirectory()) + val destination = File(PathUtil.getBaseCopyDirectory(context)) destination.let { if (it.exists()) { @@ -55,7 +55,7 @@ class ApkCopier(private val context: Context, private val packageName: String) { /*Add Split APKs*/ fileList.addAll(getSplitAPKs(packageInfo)) } - bundleAllAPKs(fileList) + bundleAllAPKs(context, fileList) } private fun getBaseApk(packageInfo: PackageInfo?): File? { @@ -75,10 +75,10 @@ class ApkCopier(private val context: Context, private val packageName: String) { return fileList } - private fun bundleAllAPKs(fileList: List) { + private fun bundleAllAPKs(context: Context, fileList: List) { try { val fileOutputStream = - FileOutputStream(PathUtil.getBaseCopyDirectory() + "$packageName.zip") + FileOutputStream(PathUtil.getBaseCopyDirectory(context) + "$packageName.zip") val zipOutputStream = ZipOutputStream(fileOutputStream) for (file in fileList) { diff --git a/app/src/main/java/com/aurora/store/util/PathUtil.kt b/app/src/main/java/com/aurora/store/util/PathUtil.kt index 0594cbdcc..6b876e971 100644 --- a/app/src/main/java/com/aurora/store/util/PathUtil.kt +++ b/app/src/main/java/com/aurora/store/util/PathUtil.kt @@ -21,6 +21,7 @@ package com.aurora.store.util import android.content.Context import android.os.Environment +import com.aurora.extensions.isRAndAbove import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.File import java.util.UUID @@ -33,7 +34,7 @@ object PathUtil { private fun getDownloadDirectory(context: Context): String { return if (context.isExternalStorageEnable()) { - getExternalPath() + getExternalPath(context) } else { context.getInternalBaseDirectory() } @@ -59,15 +60,22 @@ object PathUtil { return getVersionDirectory(context, packageName, versionCode) } - fun getExternalPath(): String { - val auroraDir = + fun getExternalPath(context: Context): String { + val defaultDir = java.io.File("${Environment.getExternalStorageDirectory().absolutePath}/Aurora/Store") - auroraDir.mkdirs() - return auroraDir.absolutePath + + if (!defaultDir.exists()) + defaultDir.mkdirs() + + return Preferences.getString( + context, + Preferences.PREFERENCE_DOWNLOAD_DIRECTORY, + defaultDir.absolutePath + ) } - fun getBaseCopyDirectory(): String { - return "${getExternalPath()}/Exports/" + fun getBaseCopyDirectory(context: Context): String { + return "${getExternalPath(context)}/Exports/" } private fun getObbDownloadPath(app: App): String { @@ -94,6 +102,20 @@ object PathUtil { file.createNewFile() return file } + + fun canWriteToDirectory(context: Context, directoryPath: String): Boolean { + val directory = if (directoryPath.startsWith("/")) { + java.io.File(directoryPath) + } else { + java.io.File(context.getExternalFilesDir(null), directoryPath) + } + + return if (isRAndAbove()) { + Environment.isExternalStorageManager() && directory.exists() && directory.canWrite() + } else { + isExternalStorageAccessible(context) && directory.exists() && directory.canWrite() + } + } } fun Context.isExternalStorageEnable(): Boolean { diff --git a/app/src/main/java/com/aurora/store/util/PermissionUtil.kt b/app/src/main/java/com/aurora/store/util/PermissionUtil.kt new file mode 100644 index 000000000..c42c156fd --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/PermissionUtil.kt @@ -0,0 +1,13 @@ +package com.aurora.store.util + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat + +fun isExternalStorageAccessible(context: Context): Boolean { + return ActivityCompat.checkSelfPermission( + context, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/Preferences.kt b/app/src/main/java/com/aurora/store/util/Preferences.kt index a68114d31..7030d0451 100644 --- a/app/src/main/java/com/aurora/store/util/Preferences.kt +++ b/app/src/main/java/com/aurora/store/util/Preferences.kt @@ -49,6 +49,7 @@ object Preferences { const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE" const val PREFERENCE_DOWNLOAD_EXTERNAL = "PREFERENCE_DOWNLOAD_EXTERNAL" + const val PREFERENCE_DOWNLOAD_DIRECTORY = "PREFERENCE_DOWNLOAD_DIRECTORY" const val PREFERENCE_DOWNLOAD_WIFI = "PREFERENCE_DOWNLOAD_WIFI" const val PREFERENCE_TOS_READ = "PREFERENCE_TOS_READ" @@ -89,8 +90,8 @@ object Preferences { getPrefs(context).edit().putBoolean(key, value).apply() } - fun getString(context: Context, key: String): String { - return getPrefs(context).getString(key, "").toString() + fun getString(context: Context, key: String, default: String = ""): String { + return getPrefs(context).getString(key, default).toString() } fun getInteger(context: Context, key: String): Int { diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt index 9ed150638..f5b58effb 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt @@ -20,6 +20,7 @@ package com.aurora.store.view.ui.onboarding import android.os.Bundle +import android.os.Environment import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -31,12 +32,14 @@ import com.aurora.extensions.isSAndAbove import com.aurora.store.R import com.aurora.store.data.work.UpdateWorker import com.aurora.store.databinding.FragmentOnboardingBinding +import com.aurora.store.util.PathUtil import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE import com.aurora.store.util.Preferences.PREFERENCE_AUTO_INSTALL import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_ACTIVE +import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_DIRECTORY import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_EXTERNAL import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID import com.aurora.store.util.Preferences.PREFERENCE_FILTER_GOOGLE @@ -155,6 +158,7 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) { /*Downloader*/ save(PREFERENCE_DOWNLOAD_ACTIVE, 3) save(PREFERENCE_DOWNLOAD_EXTERNAL, false) + save(PREFERENCE_DOWNLOAD_DIRECTORY, PathUtil.getExternalPath(requireContext())) /*Network*/ save(PREFERENCE_INSECURE_ANONYMOUS, false) diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt index 649bc1d26..15ffeba0c 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/PermissionsFragment.kt @@ -38,6 +38,7 @@ import com.aurora.store.BuildConfig import com.aurora.store.R import com.aurora.store.data.model.Permission import com.aurora.store.databinding.FragmentOnboardingPermissionsBinding +import com.aurora.store.util.isExternalStorageAccessible import com.aurora.store.view.epoxy.views.preference.PermissionViewModel_ import com.aurora.store.view.ui.commons.BaseFragment @@ -113,10 +114,8 @@ class PermissionsFragment : BaseFragment(R.layout.fragment_onboarding_permission } binding.epoxyRecycler.withModels { - val writeExternalStorage = if (!isRAndAbove()) ActivityCompat.checkSelfPermission( - requireContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED else true + val writeExternalStorage = + if (!isRAndAbove()) isExternalStorageAccessible(requireContext()) else true val postNotifications = if (isTAndAbove()) ActivityCompat.checkSelfPermission( requireContext(), Manifest.permission.POST_NOTIFICATIONS diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt index ddfee9501..3fb02e8e6 100644 --- a/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt @@ -19,18 +19,61 @@ package com.aurora.store.view.ui.preferences +import android.Manifest +import android.content.Intent import android.os.Bundle +import android.os.Environment +import android.provider.Settings import android.view.View +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.Toolbar import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat +import com.aurora.extensions.isRAndAbove +import com.aurora.extensions.toast import com.aurora.store.R +import com.aurora.store.util.PathUtil import com.aurora.store.util.Preferences +import com.aurora.store.util.isExternalStorageAccessible +import com.aurora.store.util.save class DownloadPreference : PreferenceFragmentCompat() { + private lateinit var startForStorageManagerResult: ActivityResultLauncher + private lateinit var startForPermissions: ActivityResultLauncher + + private var downloadDirectoryPreference: Preference? = null + private var downloadExternalPreference: SwitchPreferenceCompat? = null + private var autoDeletePreference: SwitchPreferenceCompat? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + startForStorageManagerResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + val state = isRAndAbove() && Environment.isExternalStorageManager() + if (state) { + downloadDirectoryPreference?.summary = + PathUtil.getExternalPath(requireContext()) + } else { + notifyPermissionState(isRAndAbove() && Environment.isExternalStorageManager()) + } + } + + startForPermissions = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { + if (it) { + notifyPermissionState(it) + } else { + downloadExternalPreference?.isChecked = false + } + } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences_download, rootKey) } @@ -43,17 +86,41 @@ class DownloadPreference : PreferenceFragmentCompat() { setNavigationOnClickListener { findNavController().navigateUp() } } - val downloadExternalPreference: SwitchPreferenceCompat? = - findPreference(Preferences.PREFERENCE_DOWNLOAD_EXTERNAL) - - val autoDeletePreference: SwitchPreferenceCompat? = - findPreference(Preferences.PREFERENCE_AUTO_DELETE) + downloadDirectoryPreference = findPreference(Preferences.PREFERENCE_DOWNLOAD_DIRECTORY) + downloadExternalPreference = findPreference(Preferences.PREFERENCE_DOWNLOAD_EXTERNAL) + autoDeletePreference = findPreference(Preferences.PREFERENCE_AUTO_DELETE) + downloadDirectoryPreference?.let { preference -> + preference.summary = PathUtil.getExternalPath(requireContext()) + preference.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { it, newValue -> + if (PathUtil.canWriteToDirectory(requireContext(), newValue.toString())) { + it.summary = newValue.toString() + save(Preferences.PREFERENCE_DOWNLOAD_DIRECTORY, newValue.toString()) + true + } else { + toast(R.string.pref_download_directory_error) + false + } + } + } downloadExternalPreference?.let { switchPreferenceCompat -> switchPreferenceCompat.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> val checked = newValue.toString().toBoolean() + + if (checked) { + if (isRAndAbove() && !Environment.isExternalStorageManager()) { + startForStorageManagerResult.launch( + Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) + ) + } + + if (!isRAndAbove() && !isExternalStorageAccessible(requireContext())) { + startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + } autoDeletePreference?.let { if (checked) { it.isEnabled = true @@ -67,4 +134,18 @@ class DownloadPreference : PreferenceFragmentCompat() { } } } -} \ No newline at end of file + + private fun notifyPermissionState(state: Boolean) { + if (state) { + toast(R.string.toast_permission_granted) + } else { + toast(R.string.permissions_denied) + } + } + + override fun onDestroy() { + super.onDestroy() + startForStorageManagerResult.unregister() + startForPermissions.unregister() + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 90d756f58..73c81452c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -131,7 +131,7 @@ "Cancel all" "Cancelled" "Clear completed" - Download completed + "Download completed" "Calculating" %1$dh %2$dm %3$ds left %1$dm %2$ds left @@ -214,6 +214,8 @@ "Active download" "User external storage" External storage is used for downloads, exports, etc. + "Download path" + "Selected path is not writable" "Removes F-Droid apps from app lists" "Filter F-Droid apps" "Removes all Google Apps from search results and category apps" diff --git a/app/src/main/res/xml/preferences_download.xml b/app/src/main/res/xml/preferences_download.xml index 8b8eac87e..5dbd8aa4b 100644 --- a/app/src/main/res/xml/preferences_download.xml +++ b/app/src/main/res/xml/preferences_download.xml @@ -20,9 +20,16 @@ + +