Android 11 support
This commit is contained in:
parent
25dbcca854
commit
d2f11c85d1
@ -27,7 +27,7 @@ android {
|
|||||||
applicationId "sushi.hardcore.droidfs"
|
applicationId "sushi.hardcore.droidfs"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
//noinspection ExpiredTargetSdkVersion
|
//noinspection ExpiredTargetSdkVersion
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 29
|
versionCode 29
|
||||||
versionName "2.0.0-alpha2"
|
versionName "2.0.0-alpha2"
|
||||||
|
|
||||||
@ -88,18 +88,18 @@ dependencies {
|
|||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation 'androidx.core:core-ktx:1.9.0'
|
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.constraintlayout:constraintlayout:2.1.4"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
|
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.preference:preference-ktx:1.2.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.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 "com.github.bumptech.glide:glide:4.13.2"
|
||||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
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-core:$exoplayer_version"
|
||||||
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
|
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
|
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
|
||||||
android:protectionLevel="signature" />
|
android:protectionLevel="signature" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
@ -2,11 +2,14 @@ package sushi.hardcore.droidfs.add_volume
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.Settings
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -77,6 +80,16 @@ class SelectPathFragment: Fragment() {
|
|||||||
return binding.root
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
originalRememberVolume = sharedPrefs.getBoolean(ConstValues.REMEMBER_VOLUME_KEY, true)
|
originalRememberVolume = sharedPrefs.getBoolean(ConstValues.REMEMBER_VOLUME_KEY, true)
|
||||||
@ -98,26 +111,30 @@ class SelectPathFragment: Fragment() {
|
|||||||
refreshStatus(binding.editVolumeName.text)
|
refreshStatus(binding.editVolumeName.text)
|
||||||
}
|
}
|
||||||
binding.buttonPickDirectory.setOnClickListener {
|
binding.buttonPickDirectory.setOnClickListener {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (!requestManageAllFiles()) {
|
||||||
if (ContextCompat.checkSelfPermission(
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
requireContext(),
|
if (
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
ContextCompat.checkSelfPermission(
|
||||||
) +
|
requireContext(),
|
||||||
ContextCompat.checkSelfPermission(
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
requireContext(),
|
) + ContextCompat.checkSelfPermission(
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
requireContext(),
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
)
|
|
||||||
launchPickDirectory()
|
|
||||||
else
|
|
||||||
askStoragePermissions.launch(
|
|
||||||
arrayOf(
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
launchPickDirectory()
|
||||||
|
} else {
|
||||||
|
askStoragePermissions.launch(
|
||||||
|
arrayOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
} else
|
} else {
|
||||||
launchPickDirectory()
|
launchPickDirectory()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
binding.editVolumeName.addTextChangedListener(object: TextWatcher {
|
binding.editVolumeName.addTextChangedListener(object: TextWatcher {
|
||||||
override fun afterTextChanged(s: Editable?) {}
|
override fun afterTextChanged(s: Editable?) {}
|
||||||
|
@ -3,9 +3,11 @@ package sushi.hardcore.droidfs.util
|
|||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
import android.os.storage.StorageManager
|
import android.os.storage.StorageManager
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
@ -18,6 +20,7 @@ import kotlin.math.pow
|
|||||||
|
|
||||||
object PathUtils {
|
object PathUtils {
|
||||||
const val SEPARATOR = '/'
|
const val SEPARATOR = '/'
|
||||||
|
const val PATH_RESOLVER_TAG = "PATH RESOLVER"
|
||||||
|
|
||||||
fun getParentPath(path: String): String {
|
fun getParentPath(path: String): String {
|
||||||
val strippedPath = if (path.endsWith(SEPARATOR)) {
|
val strippedPath = if (path.endsWith(SEPARATOR)) {
|
||||||
@ -95,11 +98,44 @@ object PathUtils {
|
|||||||
return "Android/data/${context.packageName}/"
|
return "Android/data/${context.packageName}/"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExternalStoragePath(context: Context): List<String> {
|
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<String> {
|
||||||
val externalPaths: MutableList<String> = ArrayList()
|
val externalPaths: MutableList<String> = ArrayList()
|
||||||
ContextCompat.getExternalFilesDirs(context, null).forEach {
|
ContextCompat.getExternalFilesDirs(context, null).forEach {
|
||||||
val rootPath = it.path.substring(0, it.path.indexOf(getPackageDataFolder(context)+"files"))
|
if (Environment.isExternalStorageRemovable(it)) {
|
||||||
if (!rootPath.endsWith("/0/")){ //not primary storage
|
val rootPath = it.path.substring(0, it.path.indexOf(getPackageDataFolder(context)+"files"))
|
||||||
externalPaths.add(rootPath)
|
externalPaths.add(rootPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +143,7 @@ object PathUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isPathOnExternalStorage(path: String, context: Context): Boolean {
|
fun isPathOnExternalStorage(path: String, context: Context): Boolean {
|
||||||
getExternalStoragePath(context).forEach {
|
getExternalStoragesPaths(context).forEach {
|
||||||
if (path.startsWith(it)){
|
if (path.startsWith(it)){
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -116,18 +152,23 @@ object PathUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const val PRIMARY_VOLUME_NAME = "primary"
|
private const val PRIMARY_VOLUME_NAME = "primary"
|
||||||
fun getFullPathFromTreeUri(treeUri: Uri?, context: Context): String? {
|
fun getFullPathFromTreeUri(treeUri: Uri, context: Context): String? {
|
||||||
if (treeUri == null) return null
|
|
||||||
if ("content".equals(treeUri.scheme, ignoreCase = true)) {
|
if ("content".equals(treeUri.scheme, ignoreCase = true)) {
|
||||||
val vId = getVolumeIdFromTreeUri(treeUri)
|
val vId = getVolumeIdFromTreeUri(treeUri)
|
||||||
var volumePath = getVolumePath(vId, context) ?: return null
|
Log.d(PATH_RESOLVER_TAG, "Volume Id: $vId")
|
||||||
if (volumePath.endsWith(File.separator))
|
var volumePath = getVolumePath(vId ?: return null, context)
|
||||||
volumePath = volumePath.substring(0, volumePath.length - 1)
|
Log.d(PATH_RESOLVER_TAG, "Volume Path: $volumePath")
|
||||||
var documentPath = getDocumentPathFromTreeUri(treeUri)
|
if (volumePath == null) {
|
||||||
if (documentPath!!.endsWith(File.separator))
|
volumePath = if (vId == "primary") {
|
||||||
documentPath = documentPath.substring(0, documentPath.length - 1)
|
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()) {
|
return if (documentPath.isNotEmpty()) {
|
||||||
pathJoin(volumePath, documentPath)
|
pathJoin(volumePath!!, documentPath)
|
||||||
} else volumePath
|
} else volumePath
|
||||||
} else if ("file".equals(treeUri.scheme, ignoreCase = true)) {
|
} else if ("file".equals(treeUri.scheme, ignoreCase = true)) {
|
||||||
return treeUri.path
|
return treeUri.path
|
||||||
@ -135,7 +176,7 @@ object PathUtils {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVolumePath(volumeId: String?, context: Context): String? {
|
private fun getVolumePath(volumeId: String, context: Context): String? {
|
||||||
return try {
|
return try {
|
||||||
val mStorageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
val mStorageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||||
val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume")
|
val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume")
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.7.10"
|
ext.kotlin_version = "1.7.21"
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Wed Sep 01 11:25:55 UTC 2021
|
#Wed Sep 01 11:25:55 UTC 2021
|
||||||
distributionBase=GRADLE_USER_HOME
|
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
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
Loading…
Reference in New Issue
Block a user