Skip to content

Commit bd36499

Browse files
committed
1 parent 472bb48 commit bd36499

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.osfans.trime.util
2+
3+
import android.content.Context
4+
import android.net.Uri
5+
import android.os.Environment
6+
import android.os.storage.StorageManager
7+
import android.provider.DocumentsContract
8+
import timber.log.Timber
9+
import java.io.File
10+
import java.lang.reflect.Array
11+
12+
/**
13+
* Utils for dealing with Storage Access Framework URIs.
14+
*/
15+
object UriUtils {
16+
17+
private const val HOME_VOLUME_NAME = "home"
18+
private const val DOWNLOADS_VOLUME_NAME = "downloads"
19+
private const val PRIMARY_VOLUME_NAME = "primary"
20+
21+
@JvmStatic
22+
fun Uri?.toFile(): File? {
23+
this ?: return null
24+
25+
// Determine volume id, e.g. "home", "downloads", ...
26+
val docId = DocumentsContract.getDocumentId(this)
27+
val docIdSplit = docId.split(':')
28+
val volumeId = if (docIdSplit.isNotEmpty()) docIdSplit[0] else return null
29+
Timber.d("docId: $docId, volumeId: $volumeId")
30+
31+
// Handle Uri referring to internal or external storage
32+
val volumePath = runCatching {
33+
when (volumeId) {
34+
HOME_VOLUME_NAME -> {
35+
Timber.v("Volume path: isHomeVolume")
36+
@Suppress("DEPRECATION")
37+
// Reading the environment var avoids hard coding the case of the "documents" folder.
38+
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath
39+
}
40+
DOWNLOADS_VOLUME_NAME -> {
41+
Timber.v("Volume path: isDownloadsVolume")
42+
@Suppress("DEPRECATION")
43+
// Reading the environment var avoids hard coding the case of the "downloads" folder.
44+
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath
45+
}
46+
else -> {
47+
val sm = appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager
48+
val storageVolume = Class.forName("android.os.storage.StorageVolume")
49+
val getVolumeList = sm::class.java.getMethod("getVolumeList")
50+
val getUuid = storageVolume.getMethod("getUuid")
51+
val getPath = storageVolume.getMethod("getPath")
52+
val isPrimary = storageVolume.getMethod("isPrimary")
53+
54+
val result = getVolumeList.invoke(sm)
55+
val resultSize = Array.getLength(result!!)
56+
57+
var final: String? = null
58+
for (i in 0 until resultSize) {
59+
val e = Array.get(result, i)
60+
val uuid = getUuid.invoke(e) as? String
61+
val primary = isPrimary.invoke(e) as Boolean
62+
val isPrimaryVolume = primary && PRIMARY_VOLUME_NAME == volumeId
63+
val isExternalVolume = uuid == volumeId
64+
65+
Timber.d(
66+
"Found volume: UUID = $uuid, " +
67+
"volumeId = $volumeId, " +
68+
"primary = $primary, " +
69+
"isPrimaryVolume = $isPrimaryVolume, " +
70+
"isExternalVolume = $isExternalVolume"
71+
)
72+
73+
if (isPrimaryVolume || isExternalVolume) {
74+
Timber.v("Volume path: isPrimaryVolume OR isExternalVolume")
75+
// Return path if the correct volume corresponding to volumeId was found
76+
final = getPath.invoke(e) as String
77+
break
78+
}
79+
}
80+
final ?: return File(File.separator)
81+
}
82+
}
83+
}.mapCatching {
84+
if (it.endsWith(File.separator))
85+
it.substringBeforeLast(File.separator)
86+
else it
87+
}.getOrElse {
88+
Timber.w(it, "volumePath: EXCEPTION on parsing!")
89+
Timber.e("volumePath: parse failed, volumeId = $volumeId")
90+
return File(File.separator)
91+
}
92+
93+
val docTreeId = DocumentsContract.getTreeDocumentId(this)
94+
val docTreeIdSplit = docTreeId.split(':')
95+
val documentPath = if (docTreeIdSplit.size >= 2) {
96+
docTreeIdSplit[1].let {
97+
if (it.endsWith(File.separator))
98+
it.substringBeforeLast(File.separator)
99+
else it
100+
}
101+
} else {
102+
""
103+
}
104+
Timber.d("docTreeId: $docTreeId, documentPath: $documentPath")
105+
106+
return if (documentPath.isNotEmpty()) {
107+
if (documentPath.startsWith(File.separator)) {
108+
File("$volumePath$documentPath")
109+
} else {
110+
File("$volumePath${File.separator}$documentPath")
111+
}
112+
} else {
113+
File(volumePath)
114+
}
115+
}
116+
117+
@JvmStatic
118+
fun File?.toUri(): Uri? {
119+
return runCatching {
120+
val externalFileDirs = appContext.getExternalFilesDirs(null)
121+
.filter { it != appContext.getExternalFilesDir(null) }
122+
if (externalFileDirs.isEmpty()) {
123+
Timber.w("Could not determine app's private files directory on external storage")
124+
return null
125+
}
126+
127+
val absPath = externalFileDirs[0].absolutePath
128+
val segments = absPath.split('/')
129+
if (segments.size < 2) {
130+
Timber.w("Could not extract volumeId from app's private files path '$absPath'")
131+
return null
132+
}
133+
134+
val volumeId = segments[2]
135+
return Uri.parse("content://com.android.externalstorage.documents/document/$volumeId%3A/")
136+
}.getOrDefault(null)
137+
}
138+
}

0 commit comments

Comments
 (0)