Allow choosing custom download directory

This commit is contained in:
Rahul Patel
2023-10-17 05:44:34 +05:30
parent 13f1459827
commit 4fe7d453f5
9 changed files with 153 additions and 24 deletions

View File

@@ -31,7 +31,7 @@ import java.util.zip.ZipOutputStream
class ApkCopier(private val context: Context, private val packageName: String) { class ApkCopier(private val context: Context, private val packageName: String) {
fun copy() { fun copy() {
val destination = File(PathUtil.getBaseCopyDirectory()) val destination = File(PathUtil.getBaseCopyDirectory(context))
destination.let { destination.let {
if (it.exists()) { if (it.exists()) {
@@ -55,7 +55,7 @@ class ApkCopier(private val context: Context, private val packageName: String) {
/*Add Split APKs*/ /*Add Split APKs*/
fileList.addAll(getSplitAPKs(packageInfo)) fileList.addAll(getSplitAPKs(packageInfo))
} }
bundleAllAPKs(fileList) bundleAllAPKs(context, fileList)
} }
private fun getBaseApk(packageInfo: PackageInfo?): File? { private fun getBaseApk(packageInfo: PackageInfo?): File? {
@@ -75,10 +75,10 @@ class ApkCopier(private val context: Context, private val packageName: String) {
return fileList return fileList
} }
private fun bundleAllAPKs(fileList: List<File?>) { private fun bundleAllAPKs(context: Context, fileList: List<File?>) {
try { try {
val fileOutputStream = val fileOutputStream =
FileOutputStream(PathUtil.getBaseCopyDirectory() + "$packageName.zip") FileOutputStream(PathUtil.getBaseCopyDirectory(context) + "$packageName.zip")
val zipOutputStream = ZipOutputStream(fileOutputStream) val zipOutputStream = ZipOutputStream(fileOutputStream)
for (file in fileList) { for (file in fileList) {

View File

@@ -21,6 +21,7 @@ package com.aurora.store.util
import android.content.Context import android.content.Context
import android.os.Environment import android.os.Environment
import com.aurora.extensions.isRAndAbove
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.File
import java.util.UUID import java.util.UUID
@@ -33,7 +34,7 @@ object PathUtil {
private fun getDownloadDirectory(context: Context): String { private fun getDownloadDirectory(context: Context): String {
return if (context.isExternalStorageEnable()) { return if (context.isExternalStorageEnable()) {
getExternalPath() getExternalPath(context)
} else { } else {
context.getInternalBaseDirectory() context.getInternalBaseDirectory()
} }
@@ -59,15 +60,22 @@ object PathUtil {
return getVersionDirectory(context, packageName, versionCode) return getVersionDirectory(context, packageName, versionCode)
} }
fun getExternalPath(): String { fun getExternalPath(context: Context): String {
val auroraDir = val defaultDir =
java.io.File("${Environment.getExternalStorageDirectory().absolutePath}/Aurora/Store") 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 { fun getBaseCopyDirectory(context: Context): String {
return "${getExternalPath()}/Exports/" return "${getExternalPath(context)}/Exports/"
} }
private fun getObbDownloadPath(app: App): String { private fun getObbDownloadPath(app: App): String {
@@ -94,6 +102,20 @@ object PathUtil {
file.createNewFile() file.createNewFile()
return file 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 { fun Context.isExternalStorageEnable(): Boolean {

View File

@@ -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
}

View File

@@ -49,6 +49,7 @@ object Preferences {
const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE" const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE"
const val PREFERENCE_DOWNLOAD_EXTERNAL = "PREFERENCE_DOWNLOAD_EXTERNAL" 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_DOWNLOAD_WIFI = "PREFERENCE_DOWNLOAD_WIFI"
const val PREFERENCE_TOS_READ = "PREFERENCE_TOS_READ" const val PREFERENCE_TOS_READ = "PREFERENCE_TOS_READ"
@@ -89,8 +90,8 @@ object Preferences {
getPrefs(context).edit().putBoolean(key, value).apply() getPrefs(context).edit().putBoolean(key, value).apply()
} }
fun getString(context: Context, key: String): String { fun getString(context: Context, key: String, default: String = ""): String {
return getPrefs(context).getString(key, "").toString() return getPrefs(context).getString(key, default).toString()
} }
fun getInteger(context: Context, key: String): Int { fun getInteger(context: Context, key: String): Int {

View File

@@ -20,6 +20,7 @@
package com.aurora.store.view.ui.onboarding package com.aurora.store.view.ui.onboarding
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
@@ -31,12 +32,14 @@ import com.aurora.extensions.isSAndAbove
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.work.UpdateWorker import com.aurora.store.data.work.UpdateWorker
import com.aurora.store.databinding.FragmentOnboardingBinding import com.aurora.store.databinding.FragmentOnboardingBinding
import com.aurora.store.util.PathUtil
import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE 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_AUTO_INSTALL
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT 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_DEFAULT_SELECTED_TAB
import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_ACTIVE 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_DOWNLOAD_EXTERNAL
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_GOOGLE import com.aurora.store.util.Preferences.PREFERENCE_FILTER_GOOGLE
@@ -155,6 +158,7 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) {
/*Downloader*/ /*Downloader*/
save(PREFERENCE_DOWNLOAD_ACTIVE, 3) save(PREFERENCE_DOWNLOAD_ACTIVE, 3)
save(PREFERENCE_DOWNLOAD_EXTERNAL, false) save(PREFERENCE_DOWNLOAD_EXTERNAL, false)
save(PREFERENCE_DOWNLOAD_DIRECTORY, PathUtil.getExternalPath(requireContext()))
/*Network*/ /*Network*/
save(PREFERENCE_INSECURE_ANONYMOUS, false) save(PREFERENCE_INSECURE_ANONYMOUS, false)

View File

@@ -38,6 +38,7 @@ import com.aurora.store.BuildConfig
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.model.Permission import com.aurora.store.data.model.Permission
import com.aurora.store.databinding.FragmentOnboardingPermissionsBinding 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.epoxy.views.preference.PermissionViewModel_
import com.aurora.store.view.ui.commons.BaseFragment import com.aurora.store.view.ui.commons.BaseFragment
@@ -113,10 +114,8 @@ class PermissionsFragment : BaseFragment(R.layout.fragment_onboarding_permission
} }
binding.epoxyRecycler.withModels { binding.epoxyRecycler.withModels {
val writeExternalStorage = if (!isRAndAbove()) ActivityCompat.checkSelfPermission( val writeExternalStorage =
requireContext(), if (!isRAndAbove()) isExternalStorageAccessible(requireContext()) else true
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED else true
val postNotifications = if (isTAndAbove()) ActivityCompat.checkSelfPermission( val postNotifications = if (isTAndAbove()) ActivityCompat.checkSelfPermission(
requireContext(), requireContext(),
Manifest.permission.POST_NOTIFICATIONS Manifest.permission.POST_NOTIFICATIONS

View File

@@ -19,18 +19,61 @@
package com.aurora.store.view.ui.preferences package com.aurora.store.view.ui.preferences
import android.Manifest
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.view.View import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import com.aurora.extensions.isRAndAbove
import com.aurora.extensions.toast
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.util.PathUtil
import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences
import com.aurora.store.util.isExternalStorageAccessible
import com.aurora.store.util.save
class DownloadPreference : PreferenceFragmentCompat() { class DownloadPreference : PreferenceFragmentCompat() {
private lateinit var startForStorageManagerResult: ActivityResultLauncher<Intent>
private lateinit var startForPermissions: ActivityResultLauncher<String>
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?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_download, rootKey) setPreferencesFromResource(R.xml.preferences_download, rootKey)
} }
@@ -43,17 +86,41 @@ class DownloadPreference : PreferenceFragmentCompat() {
setNavigationOnClickListener { findNavController().navigateUp() } setNavigationOnClickListener { findNavController().navigateUp() }
} }
val downloadExternalPreference: SwitchPreferenceCompat? = downloadDirectoryPreference = findPreference(Preferences.PREFERENCE_DOWNLOAD_DIRECTORY)
findPreference(Preferences.PREFERENCE_DOWNLOAD_EXTERNAL) downloadExternalPreference = findPreference(Preferences.PREFERENCE_DOWNLOAD_EXTERNAL)
autoDeletePreference = findPreference(Preferences.PREFERENCE_AUTO_DELETE)
val autoDeletePreference: SwitchPreferenceCompat? =
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 -> downloadExternalPreference?.let { switchPreferenceCompat ->
switchPreferenceCompat.onPreferenceChangeListener = switchPreferenceCompat.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue -> Preference.OnPreferenceChangeListener { _, newValue ->
val checked = newValue.toString().toBoolean() 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 { autoDeletePreference?.let {
if (checked) { if (checked) {
it.isEnabled = true it.isEnabled = true
@@ -67,4 +134,18 @@ class DownloadPreference : PreferenceFragmentCompat() {
} }
} }
} }
}
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()
}
}

View File

@@ -131,7 +131,7 @@
<string name="download_cancel_all">"Cancel all"</string> <string name="download_cancel_all">"Cancel all"</string>
<string name="download_canceled">"Cancelled"</string> <string name="download_canceled">"Cancelled"</string>
<string name="download_clear_completed">"Clear completed"</string> <string name="download_clear_completed">"Clear completed"</string>
<string name="download_completed">Download completed</string> <string name="download_completed">"Download completed"</string>
<string name="download_eta_calculating">"Calculating"</string> <string name="download_eta_calculating">"Calculating"</string>
<string name="download_eta_hrs">%1$dh %2$dm %3$ds left</string> <string name="download_eta_hrs">%1$dh %2$dm %3$ds left</string>
<string name="download_eta_min">%1$dm %2$ds left</string> <string name="download_eta_min">%1$dm %2$ds left</string>
@@ -214,6 +214,8 @@
<string name="pref_downloader_active_title">"Active download"</string> <string name="pref_downloader_active_title">"Active download"</string>
<string name="pref_downloader_external">"User external storage"</string> <string name="pref_downloader_external">"User external storage"</string>
<string name="pref_downloader_external_desc">External storage is used for downloads, exports, etc.</string> <string name="pref_downloader_external_desc">External storage is used for downloads, exports, etc.</string>
<string name="pref_downloader_path_title">"Download path"</string>
<string name="pref_download_directory_error">"Selected path is not writable"</string>
<string name="pref_filter_fdroid_summary">"Removes F-Droid apps from app lists"</string> <string name="pref_filter_fdroid_summary">"Removes F-Droid apps from app lists"</string>
<string name="pref_filter_fdroid_title">"Filter F-Droid apps"</string> <string name="pref_filter_fdroid_title">"Filter F-Droid apps"</string>
<string name="pref_filter_google_summary">"Removes all Google Apps from search results and category apps"</string> <string name="pref_filter_google_summary">"Removes all Google Apps from search results and category apps"</string>

View File

@@ -20,9 +20,16 @@
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
app:iconSpaceReserved="false"
app:dependency="PREFERENCE_DOWNLOAD_EXTERNAL"
app:key="PREFERENCE_DOWNLOAD_DIRECTORY"
app:title="@string/pref_downloader_path_title" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="false" app:defaultValue="false"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:disableDependentsState="false"
app:key="PREFERENCE_DOWNLOAD_EXTERNAL" app:key="PREFERENCE_DOWNLOAD_EXTERNAL"
app:summary="@string/pref_downloader_external_desc" app:summary="@string/pref_downloader_external_desc"
app:title="@string/pref_downloader_external" /> app:title="@string/pref_downloader_external" />