Android 11 support

This commit is contained in:
Matéo Duparc 2023-02-01 19:08:14 +01:00
parent 25dbcca854
commit d2f11c85d1
Signed by untrusted user: hardcoresushi
GPG Key ID: AFE384344A45E13A
6 changed files with 99 additions and 40 deletions

View File

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

View File

@ -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" />

View File

@ -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,27 +111,31 @@ class SelectPathFragment: Fragment() {
refreshStatus(binding.editVolumeName.text) refreshStatus(binding.editVolumeName.text)
} }
binding.buttonPickDirectory.setOnClickListener { binding.buttonPickDirectory.setOnClickListener {
if (!requestManageAllFiles()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission( if (
ContextCompat.checkSelfPermission(
requireContext(), requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.READ_EXTERNAL_STORAGE
) + ) + ContextCompat.checkSelfPermission(
ContextCompat.checkSelfPermission(
requireContext(), requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED ) == PackageManager.PERMISSION_GRANTED
) ) {
launchPickDirectory() launchPickDirectory()
else } else {
askStoragePermissions.launch( askStoragePermissions.launch(
arrayOf( arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_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?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

View File

@ -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 {
if (Environment.isExternalStorageRemovable(it)) {
val rootPath = it.path.substring(0, it.path.indexOf(getPackageDataFolder(context)+"files")) val rootPath = it.path.substring(0, it.path.indexOf(getPackageDataFolder(context)+"files"))
if (!rootPath.endsWith("/0/")){ //not primary storage
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")

View File

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

View File

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