fix file utils, move them to a separate class

This commit is contained in:
Fleeym authored and Fleeym committed 2024-01-24 04:45:09 +02:00
1 parent 498bbf4528
commit dcfab5e001
6 files changed
+405 -232

No files matched your search

@@ -0,0 +1,34 @@
package com.geode.launcher.activityresult
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import androidx.activity.result.contract.ActivityResultContract
open class GeodeOpenFileActivityResult : ActivityResultContract<GeodeOpenFileActivityResult.OpenFileParams, Uri?>() {
class OpenFileParams(val extraMimes: Array<String>, val defaultPath: Uri?) {}
override fun createIntent(context: Context, input: OpenFileParams): Intent {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.putExtra(Intent.EXTRA_MIME_TYPES, input.extraMimes)
.setType("*/*")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (input.defaultPath != null) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input.defaultPath)
}
}
return intent
}
final override fun getSynchronousResult(
context: Context,
input: OpenFileParams
): SynchronousResult<Uri?>? = null
final override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
}
}
@@ -0,0 +1,53 @@
package com.geode.launcher.activityresult
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import androidx.activity.result.contract.ActivityResultContract
open class GeodeOpenFilesActivityResult : ActivityResultContract<GeodeOpenFilesActivityResult.OpenFileParams, List<@JvmSuppressWildcards Uri>>() {
class OpenFileParams(val extraMimes: Array<String>, val defaultPath: Uri?) {}
override fun createIntent(context: Context, input: OpenFileParams): Intent {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.putExtra(Intent.EXTRA_MIME_TYPES, input.extraMimes)
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
.setType("*/*")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (input.defaultPath != null) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input.defaultPath)
}
}
return intent
}
final override fun getSynchronousResult(
context: Context,
input: OpenFileParams
): SynchronousResult<List<@JvmSuppressWildcards Uri>>? = null
final override fun parseResult(resultCode: Int, intent: Intent?): List<Uri> {
// Gracefully stolen from ActivityResultContracts.Intent.getclipDataUris
val clipData = intent.takeIf {
resultCode == Activity.RESULT_OK
}?.clipData
val resultSet = LinkedHashSet<Uri>()
intent?.data?.let { data ->
resultSet.add(data)
}
if (clipData == null && resultSet.isEmpty()) {
return emptyList()
} else if (clipData != null) {
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
if (uri != null) {
resultSet.add(uri)
}
}
}
return ArrayList(resultSet)
}
}
@@ -0,0 +1,33 @@
package com.geode.launcher.activityresult
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import androidx.activity.result.contract.ActivityResultContract
open class GeodeSaveFileActivityResult : ActivityResultContract<GeodeSaveFileActivityResult.SaveFileParams, Uri?>() {
class SaveFileParams(val mimeType: String?, val defaultPath: Uri?) {}
override fun createIntent(context: Context, input: SaveFileParams): Intent {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
.setType(input.mimeType ?: "*/*")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (input.defaultPath != null) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input.defaultPath)
}
}
return intent
}
final override fun getSynchronousResult(
context: Context,
input: SaveFileParams
): SynchronousResult<Uri?>? = null
final override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
}
}
@@ -0,0 +1,196 @@
package com.geode.launcher.utils
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import java.io.File
class FileUtils {
// copied from https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android
// i am actually very lazy to move this to a separate class
// hello zmx i moved it
companion object {
fun getRealPathFromURI(context: Context, uri: Uri): String? {
when {
// DocumentProvider
DocumentsContract.isDocumentUri(context, uri) -> {
when {
// ExternalStorageProvider
isExternalStorageDocument(uri) -> {
return handleExternalStorageDocument(context, uri)
}
isDownloadsDocument(uri) -> {
return handleDownloadsDocument(context, uri)
}
isMediaDocument(uri) -> {
return handleMediaDocument(context, uri)
}
// TODO: add the geode provider
}
}
"content".equals(uri.scheme, ignoreCase = true) -> {
// Return the remote address
if (isGooglePhotosUri(uri)) return uri.lastPathSegment
// This happened with a directory
if (isExternalStorageDocument(uri)) {
return handleExternalStorageDocument(context, uri)
}
return getDataColumn(context, uri, null, null)
}
"file".equals(uri.scheme, ignoreCase = true) -> {
return uri.path
}
}
return null
}
private fun handleMediaDocument(context: Context, uri: Uri): String? {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
var contentUri: Uri? = null
when (type) {
"image" -> {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
"video" -> {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
"audio" -> {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(
context,
contentUri,
selection,
selectionArgs
)
}
private fun handleDownloadsDocument(context: Context, uri: Uri): String? {
val fileName = getFilePath(context, uri)
if (fileName != null) {
return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
}
var id = DocumentsContract.getDocumentId(uri)
if (id.startsWith("raw:")) {
id = id.replaceFirst("raw:".toRegex(), "")
val file = File(id)
if (file.exists()) return id
}
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
}
private fun handleExternalStorageDocument(context: Context, uri: Uri): String {
val docId: String = if (DocumentsContract.isDocumentUri(context, uri)) {
DocumentsContract.getDocumentId(uri)
} else {
// please please please PLEASE let this be the only other case
DocumentsContract.getTreeDocumentId(uri)
}
val split = docId.split(":").toTypedArray()
val type = split[0]
// This is for checking Main Memory
return if ("primary".equals(type, ignoreCase = true)) {
if (split.size > 1) {
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
} else {
Environment.getExternalStorageDirectory().toString() + "/"
}
// This is for checking SD Card
} else {
"storage" + "/" + docId.replace(":", "/")
}
}
private fun getFilePath(context: Context, uri: Uri?): String? {
var cursor: Cursor? = null
val projection = arrayOf(
MediaStore.MediaColumns.DISPLAY_NAME
)
try {
if (uri == null) return null
cursor = context.contentResolver.query(
uri, projection, null, null,
null
)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
val data = cursor.getString(index)
cursor.close()
return data
}
} catch (e: Exception) {
cursor?.close()
} finally {
cursor?.close()
}
return null
}
private fun getDataColumn(context: Context, uri: Uri?, selection: String?,
selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(
column
)
try {
if (uri == null) return null
cursor = context.contentResolver.query(
uri, projection, selection, selectionArgs,
null
)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(column)
val data = cursor.getString(index)
cursor.close()
return data
}
} catch (e: Exception) {
cursor?.close()
}
return null
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
private fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
private fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
private fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
private fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}
}
}
@@ -1,69 +1,74 @@
package com.geode.launcher.utils
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
import android.provider.OpenableColumns
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.documentfile.provider.DocumentFile
import com.geode.launcher.activityresult.GeodeOpenFileActivityResult
import com.geode.launcher.activityresult.GeodeOpenFilesActivityResult
import com.geode.launcher.activityresult.GeodeSaveFileActivityResult
import java.io.File
import java.lang.ref.WeakReference
import java.net.URI
import kotlin.system.exitProcess
@Suppress("unused")
object GeodeUtils {
private lateinit var activity: WeakReference<AppCompatActivity>
private lateinit var fileResultLauncher: ActivityResultLauncher<Intent>
private lateinit var filesResultLauncher: ActivityResultLauncher<Intent>
private lateinit var openFileResultLauncher: ActivityResultLauncher<GeodeOpenFileActivityResult.OpenFileParams>
private lateinit var openDirectoryResultLauncher: ActivityResultLauncher<Uri?>
private lateinit var openFilesResultLauncher: ActivityResultLauncher<GeodeOpenFilesActivityResult.OpenFileParams>
private lateinit var saveFileResultLauncher: ActivityResultLauncher<GeodeSaveFileActivityResult.SaveFileParams>
fun setContext(activity: AppCompatActivity) {
this.activity = WeakReference(activity)
fileResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data
if (data != null) {
val uri = data.data
if (uri != null) {
val path = getRealPathFromURI(activity, uri)
if (path != null) {
selectFileCallback(path)
return@registerForActivityResult
}
}
openFileResultLauncher = activity.registerForActivityResult(GeodeOpenFileActivityResult()) { uri ->
if (uri != null) {
val path = FileUtils.getRealPathFromURI(activity, uri)
if (path != null) {
selectFileCallback(path)
return@registerForActivityResult
}
}
failedCallback()
}
filesResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data
if (data != null) {
val clipData = data.clipData
if (clipData != null) {
val paths = Array(clipData.itemCount) { "" }
for (i in 0 until clipData.itemCount) {
val path = getRealPathFromURI(activity, clipData.getItemAt(i).uri)
if (path != null) {
paths[i] = path
}
}
selectFilesCallback(paths)
return@registerForActivityResult
}
openDirectoryResultLauncher = activity.registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
if (it != null) {
val path = FileUtils.getRealPathFromURI(activity, it)
if (path != null)
selectFileCallback(path)
return@registerForActivityResult
}
failedCallback()
}
openFilesResultLauncher = activity.registerForActivityResult(GeodeOpenFilesActivityResult()) { result ->
if (result.isEmpty()) {
failedCallback()
return@registerForActivityResult
}
val paths: Array<String> = Array(result.size) {"n = $it"}
for (i in result.indices) {
val path = FileUtils.getRealPathFromURI(activity, result[i])
if (path != null) {
paths[i] = path
}
}
selectFilesCallback(paths)
return@registerForActivityResult
}
saveFileResultLauncher = activity.registerForActivityResult(GeodeSaveFileActivityResult()) { uri ->
if (uri != null) {
val path = FileUtils.getRealPathFromURI(activity, uri)
if (path != null) {
selectFileCallback(path)
return@registerForActivityResult
}
}
failedCallback()
@@ -72,16 +77,13 @@ object GeodeUtils {
@JvmStatic
fun getLogcatCrashBuffer(): String {
try {
return try {
val logcatProcess = Runtime.getRuntime().exec("logcat -v brief -b crash -d")
val logcatOutput = logcatProcess.inputStream.bufferedReader().readText()
return logcatOutput
}
catch (e: Exception) {
logcatProcess.inputStream.bufferedReader().readText()
} catch (e: Exception) {
Log.e("Geode", "Failed to get logcat crash buffer", e)
return ""
""
}
}
@@ -111,18 +113,19 @@ object GeodeUtils {
activity.get()?.run {
packageManager.getLaunchIntentForPackage(packageName)?.also {
val mainIntent = Intent.makeRestartActivityTask(it.component)
mainIntent.putExtra("restarted", true);
mainIntent.putExtra("restarted", true)
startActivity(mainIntent)
exitProcess(0)
}
}
}
// TODO As of now this is unused
@JvmStatic
fun openFolder(path: String): Boolean {
activity.get()?.run {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addCategory(Intent.CATEGORY_OPENABLE)
DocumentFile.fromFile(File(path)).also {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
@@ -137,28 +140,27 @@ object GeodeUtils {
return false
}
@Suppress("KotlinJniMissingFunction")
external fun selectFileCallback(path: String)
@Suppress("KotlinJniMissingFunction")
external fun selectFilesCallback(paths: Array<String>)
@Suppress("KotlinJniMissingFunction")
external fun failedCallback()
@JvmStatic
fun selectFile(path: String): Boolean {
activity.get()?.run {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_OPENABLE)
var uri: Uri?
DocumentFile.fromFile(File(path)).also {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
uri = it.uri
}
intent.setType("*/*")
if (intent.resolveActivity(packageManager) != null) {
print("Geode Selectfile")
fileResultLauncher.launch(intent)
print("Geode launched ")
try {
openFileResultLauncher.launch(GeodeOpenFileActivityResult.OpenFileParams(arrayOf("*/*"), uri))
return true
} catch (e: ActivityNotFoundException) {
return false
}
}
return false
@@ -167,18 +169,15 @@ object GeodeUtils {
@JvmStatic
fun selectFiles(path: String): Boolean {
activity.get()?.run {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_OPENABLE)
var uri: Uri?
DocumentFile.fromFile(File(path)).also {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
uri = it.uri
}
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.setType("*/*")
if (intent.resolveActivity(packageManager) != null) {
filesResultLauncher.launch(intent)
try {
openFilesResultLauncher.launch(GeodeOpenFilesActivityResult.OpenFileParams(arrayOf("*/*"), uri))
return true
} catch (e: ActivityNotFoundException) {
return false
}
}
return false
@@ -187,17 +186,15 @@ object GeodeUtils {
@JvmStatic
fun selectFolder(path: String): Boolean {
activity.get()?.run {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_OPENABLE)
var uri: Uri?
DocumentFile.fromFile(File(path)).also {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
uri = it.uri
}
intent.setType("*/*")
if (intent.resolveActivity(packageManager) != null) {
fileResultLauncher.launch(intent)
try {
openDirectoryResultLauncher.launch(uri)
return true
} catch (e: ActivityNotFoundException) {
return false
}
}
return false
@@ -206,17 +203,16 @@ object GeodeUtils {
@JvmStatic
fun createFile(path: String): Boolean {
activity.get()?.run {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_OPENABLE)
var uri: Uri?
DocumentFile.fromFile(File(path)).also {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
uri = it.uri
}
intent.setType("*/*")
if (intent.resolveActivity(packageManager) != null) {
fileResultLauncher.launch(intent)
try {
saveFileResultLauncher.launch(GeodeSaveFileActivityResult.SaveFileParams(null, uri))
return true
} catch (e: ActivityNotFoundException) {
return false
}
}
return false
@@ -250,154 +246,6 @@ object GeodeUtils {
return ""
}
// copied from https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android
// i am actually very lazy to move this to a separate class
fun getRealPathFromURI(context: Context, uri: Uri): String? {
when {
// DocumentProvider
DocumentsContract.isDocumentUri(context, uri) -> {
when {
// ExternalStorageProvider
isExternalStorageDocument(uri) -> {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
// This is for checking Main Memory
return if ("primary".equals(type, ignoreCase = true)) {
if (split.size > 1) {
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
} else {
Environment.getExternalStorageDirectory().toString() + "/"
}
// This is for checking SD Card
} else {
"storage" + "/" + docId.replace(":", "/")
}
}
isDownloadsDocument(uri) -> {
val fileName = getFilePath(context, uri)
if (fileName != null) {
return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
}
var id = DocumentsContract.getDocumentId(uri)
if (id.startsWith("raw:")) {
id = id.replaceFirst("raw:".toRegex(), "")
val file = File(id)
if (file.exists()) return id
}
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
}
isMediaDocument(uri) -> {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
var contentUri: Uri? = null
when (type) {
"image" -> {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
"video" -> {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
"audio" -> {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri, selection, selectionArgs)
}
// TODO: add the geode provider
}
}
"content".equals(uri.scheme, ignoreCase = true) -> {
// Return the remote address
if (isGooglePhotosUri(uri)) return uri.lastPathSegment
return getDataColumn(context, uri, null, null)
}
"file".equals(uri.scheme, ignoreCase = true) -> {
return uri.path
}
}
return null
}
fun getDataColumn(context: Context, uri: Uri?, selection: String?,
selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(
column
)
try {
if (uri == null) return null
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs,
null)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(column)
return cursor.getString(index)
}
} finally {
cursor?.close()
}
return null
}
fun getFilePath(context: Context, uri: Uri?): String? {
var cursor: Cursor? = null
val projection = arrayOf(
MediaStore.MediaColumns.DISPLAY_NAME
)
try {
if (uri == null) return null
cursor = context.contentResolver.query(uri, projection, null, null,
null)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
return cursor.getString(index)
}
} finally {
cursor?.close()
}
return null
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}
fun isGeodeUri(uri: Uri): Boolean {
return "com.geode.launcher.user" == uri.authority
}