diff --git a/app/build.gradle b/app/build.gradle index 44083f1..967a430 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,7 +27,7 @@ android { applicationId "sushi.hardcore.droidfs" minSdkVersion 21 //noinspection ExpiredTargetSdkVersion - targetSdkVersion 29 + targetSdkVersion 30 versionCode 29 versionName "2.0.0-alpha2" @@ -88,18 +88,18 @@ dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation 'androidx.core:core-ktx:1.9.0' - implementation "androidx.appcompat:appcompat:1.5.1" + implementation "androidx.appcompat:appcompat:1.6.0" implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" - implementation "androidx.sqlite:sqlite-ktx:2.2.0" + implementation "androidx.sqlite:sqlite-ktx:2.3.0" implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" - implementation 'com.google.android.material:material:1.6.1' + implementation 'com.google.android.material:material:1.8.0' implementation "com.github.bumptech.glide:glide:4.13.2" implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05" - def exoplayer_version = "2.18.1" + def exoplayer_version = "2.18.2" implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 60e02a4..b1c31cd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ android:name="${applicationId}.WRITE_TEMPORARY_STORAGE" android:protectionLevel="signature" /> + diff --git a/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt b/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt index c9b8fdf..3927f2f 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/add_volume/SelectPathFragment.kt @@ -2,11 +2,14 @@ package sushi.hardcore.droidfs.add_volume import android.Manifest import android.annotation.SuppressLint +import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle +import android.os.Environment +import android.provider.Settings import android.text.Editable import android.text.TextWatcher import android.view.LayoutInflater @@ -77,6 +80,16 @@ class SelectPathFragment: Fragment() { return binding.root } + private fun requestManageAllFiles(): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) { + startActivity(Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:"+requireContext().packageName))) + return true + } + } + return false + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) originalRememberVolume = sharedPrefs.getBoolean(ConstValues.REMEMBER_VOLUME_KEY, true) @@ -98,26 +111,30 @@ class SelectPathFragment: Fragment() { refreshStatus(binding.editVolumeName.text) } binding.buttonPickDirectory.setOnClickListener { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.READ_EXTERNAL_STORAGE - ) + - ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED - ) - launchPickDirectory() - else - askStoragePermissions.launch( - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, + if (!requestManageAllFiles()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if ( + ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.READ_EXTERNAL_STORAGE + ) + ContextCompat.checkSelfPermission( + requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED + ) { + launchPickDirectory() + } else { + askStoragePermissions.launch( + arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) ) - ) - } else - launchPickDirectory() + } + } else { + launchPickDirectory() + } + } } binding.editVolumeName.addTextChangedListener(object: TextWatcher { override fun afterTextChanged(s: Editable?) {} diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt index 6b54ad9..27c9936 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt @@ -3,9 +3,11 @@ package sushi.hardcore.droidfs.util import android.content.ActivityNotFoundException import android.content.Context import android.net.Uri +import android.os.Environment import android.os.storage.StorageManager import android.provider.DocumentsContract import android.provider.OpenableColumns +import android.util.Log import androidx.activity.result.ActivityResultLauncher import androidx.core.content.ContextCompat import sushi.hardcore.droidfs.R @@ -18,6 +20,7 @@ import kotlin.math.pow object PathUtils { const val SEPARATOR = '/' + const val PATH_RESOLVER_TAG = "PATH RESOLVER" fun getParentPath(path: String): String { val strippedPath = if (path.endsWith(SEPARATOR)) { @@ -95,11 +98,44 @@ object PathUtils { return "Android/data/${context.packageName}/" } - private fun getExternalStoragePath(context: Context): List { + private fun getExternalStoragePath(context: Context, name: String): String? { + for (dir in ContextCompat.getExternalFilesDirs(context, null)) { + Log.d(PATH_RESOLVER_TAG, "External dir: $dir") + if (Environment.isExternalStorageRemovable(dir)) { + Log.d(PATH_RESOLVER_TAG, "isExternalStorageRemovable") + val path = dir.path.split("/Android")[0] + if (File(path).name == name) { + return path + } + } + } + Log.d(PATH_RESOLVER_TAG, "getExternalFilesDirs failed") + try { + val process = ProcessBuilder("mount").redirectErrorStream(true).start().apply { waitFor() } + process.inputStream.readBytes().decodeToString().split("\n").forEach { line -> + if (line.startsWith("/dev/block/vold")) { + Log.d(PATH_RESOLVER_TAG, "mount: $line") + val fields = line.split(" ") + if (fields.size >= 3) { + val path = fields[2] + if (File(path).name == name) { + return path + } + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + Log.d(PATH_RESOLVER_TAG, "mount processing failed") + return null + } + + private fun getExternalStoragesPaths(context: Context): List { val externalPaths: MutableList = ArrayList() ContextCompat.getExternalFilesDirs(context, null).forEach { - val rootPath = it.path.substring(0, it.path.indexOf(getPackageDataFolder(context)+"files")) - if (!rootPath.endsWith("/0/")){ //not primary storage + if (Environment.isExternalStorageRemovable(it)) { + val rootPath = it.path.substring(0, it.path.indexOf(getPackageDataFolder(context)+"files")) externalPaths.add(rootPath) } } @@ -107,7 +143,7 @@ object PathUtils { } fun isPathOnExternalStorage(path: String, context: Context): Boolean { - getExternalStoragePath(context).forEach { + getExternalStoragesPaths(context).forEach { if (path.startsWith(it)){ return true } @@ -116,18 +152,23 @@ object PathUtils { } private const val PRIMARY_VOLUME_NAME = "primary" - fun getFullPathFromTreeUri(treeUri: Uri?, context: Context): String? { - if (treeUri == null) return null + fun getFullPathFromTreeUri(treeUri: Uri, context: Context): String? { if ("content".equals(treeUri.scheme, ignoreCase = true)) { val vId = getVolumeIdFromTreeUri(treeUri) - var volumePath = getVolumePath(vId, context) ?: return null - if (volumePath.endsWith(File.separator)) - volumePath = volumePath.substring(0, volumePath.length - 1) - var documentPath = getDocumentPathFromTreeUri(treeUri) - if (documentPath!!.endsWith(File.separator)) - documentPath = documentPath.substring(0, documentPath.length - 1) + Log.d(PATH_RESOLVER_TAG, "Volume Id: $vId") + var volumePath = getVolumePath(vId ?: return null, context) + Log.d(PATH_RESOLVER_TAG, "Volume Path: $volumePath") + if (volumePath == null) { + volumePath = if (vId == "primary") { + Environment.getExternalStorageDirectory().path + } else { + getExternalStoragePath(context, vId) ?: "/storage/$vId" + } + } + val documentPath = getDocumentPathFromTreeUri(treeUri)!! + Log.d(PATH_RESOLVER_TAG, "Document Path: $documentPath") return if (documentPath.isNotEmpty()) { - pathJoin(volumePath, documentPath) + pathJoin(volumePath!!, documentPath) } else volumePath } else if ("file".equals(treeUri.scheme, ignoreCase = true)) { return treeUri.path @@ -135,7 +176,7 @@ object PathUtils { return null } - private fun getVolumePath(volumeId: String?, context: Context): String? { + private fun getVolumePath(volumeId: String, context: Context): String? { return try { val mStorageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume") diff --git a/build.gradle b/build.gradle index 53b347b..cd441b6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = "1.7.10" + ext.kotlin_version = "1.7.21" repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:7.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 562920e..5fa549d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Sep 01 11:25:55 UTC 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME