mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-01 10:01:07 +02:00
feat(android): add Plugin assetUrl function (#6299)
This commit is contained in:
committed by
GitHub
parent
897a1547a1
commit
dffd8eb5a8
197
core/tauri/mobile/android/src/main/java/app/tauri/FsUtils.kt
Normal file
197
core/tauri/mobile/android/src/main/java/app/tauri/FsUtils.kt
Normal file
@@ -0,0 +1,197 @@
|
||||
package app.tauri
|
||||
|
||||
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 android.provider.OpenableColumns
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.math.min
|
||||
|
||||
internal class FsUtils {
|
||||
companion object {
|
||||
fun getFileUrlForUri(context: Context, uri: Uri): String? {
|
||||
// DocumentProvider
|
||||
if (DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
val docId: String = DocumentsContract.getDocumentId(uri)
|
||||
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
val type = split[0]
|
||||
if ("primary".equals(type, ignoreCase = true)) {
|
||||
return legacyPrimaryPath(split[1])
|
||||
} else {
|
||||
val splitIndex = docId.indexOf(':', 1)
|
||||
val tag = docId.substring(0, splitIndex)
|
||||
val path = docId.substring(splitIndex + 1)
|
||||
val nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag)
|
||||
if (nonPrimaryVolume != null) {
|
||||
val result = "$nonPrimaryVolume/$path"
|
||||
val file = File(result)
|
||||
return if (file.exists() && file.canRead()) {
|
||||
result
|
||||
} else null
|
||||
}
|
||||
}
|
||||
} else if (isDownloadsDocument(uri)) {
|
||||
val id: String = DocumentsContract.getDocumentId(uri)
|
||||
val contentUri: Uri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"),
|
||||
java.lang.Long.valueOf(id)
|
||||
)
|
||||
return getDataColumn(context, contentUri, null, null)
|
||||
} else if (isMediaDocument(uri)) {
|
||||
val docId: String = DocumentsContract.getDocumentId(uri)
|
||||
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.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])
|
||||
if (contentUri != null) {
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs)
|
||||
}
|
||||
}
|
||||
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
|
||||
// Return the remote address
|
||||
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(
|
||||
context,
|
||||
uri,
|
||||
null,
|
||||
null
|
||||
)
|
||||
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
|
||||
return uri.path
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the data column for this Uri. This is useful for
|
||||
* MediaStore Uris, and other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param selection (Optional) Filter used in the query.
|
||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||
* @return The value of the _data column, which is typically a file path.
|
||||
*/
|
||||
private fun getDataColumn(
|
||||
context: Context,
|
||||
uri: Uri,
|
||||
selection: String?,
|
||||
selectionArgs: Array<String>?
|
||||
): String? {
|
||||
var path: String? = null
|
||||
var cursor: Cursor? = null
|
||||
val column = "_data"
|
||||
val projection = arrayOf(column)
|
||||
try {
|
||||
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val index = cursor.getColumnIndexOrThrow(column)
|
||||
path = cursor.getString(index)
|
||||
}
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
return getCopyFilePath(uri, context)
|
||||
} finally {
|
||||
cursor?.close()
|
||||
}
|
||||
return path ?: getCopyFilePath(uri, context)
|
||||
}
|
||||
|
||||
private fun getCopyFilePath(uri: Uri, context: Context): String? {
|
||||
val cursor = context.contentResolver.query(uri, null, null, null, null)!!
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
cursor.moveToFirst()
|
||||
val name = cursor.getString(nameIndex)
|
||||
val file = File(context.filesDir, name)
|
||||
try {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
val outputStream = FileOutputStream(file)
|
||||
var read: Int
|
||||
val maxBufferSize = 1024 * 1024
|
||||
val bufferSize = min(inputStream!!.available(), maxBufferSize)
|
||||
val buffers = ByteArray(bufferSize)
|
||||
while (inputStream.read(buffers).also { read = it } != -1) {
|
||||
outputStream.write(buffers, 0, read)
|
||||
}
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
} finally {
|
||||
cursor.close()
|
||||
}
|
||||
return file.path
|
||||
}
|
||||
|
||||
private fun legacyPrimaryPath(pathPart: String): String {
|
||||
return Environment.getExternalStorageDirectory().toString() + "/" + pathPart
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
|
||||
private fun getPathToNonPrimaryVolume(context: Context, tag: String): String? {
|
||||
val volumes = context.externalCacheDirs
|
||||
if (volumes != null) {
|
||||
for (volume in volumes) {
|
||||
if (volume != null) {
|
||||
val path = volume.absolutePath
|
||||
val index = path.indexOf(tag)
|
||||
if (index != -1) {
|
||||
return path.substring(0, index) + tag
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@ package app.tauri.plugin
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.webkit.WebView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import app.tauri.FsUtils
|
||||
import app.tauri.Logger
|
||||
import app.tauri.PermissionHelper
|
||||
import app.tauri.PermissionState
|
||||
@@ -50,6 +52,17 @@ abstract class Plugin(private val activity: Activity) {
|
||||
return Logger.tags(this.javaClass.simpleName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an URI to an URL that can be loaded by the webview.
|
||||
*/
|
||||
fun assetUrl(u: Uri): String {
|
||||
var path = FsUtils.getFileUrlForUri(activity, u)
|
||||
if (path?.startsWith("file://") == true) {
|
||||
path = path.replace("file://", "")
|
||||
}
|
||||
return "asset://localhost$path"
|
||||
}
|
||||
|
||||
/**
|
||||
* Exported plugin method for checking the granted status for each permission
|
||||
* declared on the plugin. This plugin call responds with a mapping of permissions to
|
||||
|
||||
Reference in New Issue
Block a user