# HG changeset patch # User bsmith@81767d24-ef19-dc11-ae90-00e081727c95 # Date 1622163727 0 # Node ID 2c15b3d41fe423f1f5c38264e58c25721d3ad491 # Parent cc2befdc97e85b4aeb0472381d83709a6b6a3aa8 Android: Add preliminary new file browser that uses the system ACTION_GET_CONTENT Intent. This doesn't quite work right yet, so includingit as fileBrowseNew() until I can get it working well. diff -r cc2befdc97e8 -r 2c15b3d41fe4 android/DWindows.kt --- a/android/DWindows.kt Thu May 27 23:34:23 2021 +0000 +++ b/android/DWindows.kt Fri May 28 01:02:07 2021 +0000 @@ -5,19 +5,20 @@ import android.app.Dialog import android.app.NotificationChannel import android.app.NotificationManager -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.DialogInterface +import android.content.* import android.content.pm.ActivityInfo import android.content.res.Configuration +import android.database.Cursor import android.graphics.* import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.media.AudioManager import android.media.ToneGenerator +import android.net.Uri import android.os.* +import android.provider.DocumentsContract +import android.provider.MediaStore import android.text.InputFilter import android.text.InputFilter.LengthFilter import android.text.InputType @@ -740,6 +741,7 @@ private var bgcolor: Int = 0 private var menuBar: DWMenu? = null private var defaultItem: View? = null + private var fileURI: Uri? = null // Our version of runOnUiThread that waits for execution fun waitOnUiThread(runnable: Runnable) @@ -3221,6 +3223,35 @@ Log.d(null, text) } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if(requestCode == 100 && resultCode == Activity.RESULT_OK) { + fileURI = data!!.data + throw java.lang.RuntimeException() + } + } + + fun fileBrowseNew(title: String, defpath: String?, ext: String?, flags: Int): String? + { + var retval: String? = null + + waitOnUiThread { + val fileintent = Intent(Intent.ACTION_GET_CONTENT) + fileintent.type = "text/plain" + fileintent.addCategory(Intent.CATEGORY_OPENABLE) + startActivityForResult(fileintent, 100) + } + + // loop till a runtime exception is triggered. + try { + Looper.loop() + } catch (e2: RuntimeException) { + } + + retval = getUriRealPath(this, fileURI) + return retval + } + fun fileBrowse(title: String, defpath: String?, ext: String?, flags: Int): String? { var retval: String? = null @@ -3228,12 +3259,12 @@ waitOnUiThread { val fc = DWFileChooser(this) fc.setFileListener(object: DWFileChooser.FileSelectedListener { - override fun fileSelected(file: File?) { - // do something with the file - retval = file!!.absolutePath - throw java.lang.RuntimeException() - } - }) + override fun fileSelected(file: File?) { + // do something with the file + retval = file!!.absolutePath + throw java.lang.RuntimeException() + } + }) if(ext != null) { fc.setExtension(ext) } @@ -3421,6 +3452,190 @@ } /* + * This method will parse out the real local file path from the file content URI. + */ + private fun getUriRealPath(ctx: Context?, uri: Uri?): String? { + var ret: String? = "" + if (ctx != null && uri != null) { + if (isContentUri(uri)) { + if (isGooglePhotoDoc(uri.authority)) { + ret = uri.lastPathSegment + } else { + ret = getImageRealPath(contentResolver, uri, null) + } + } else if (isFileUri(uri)) { + ret = uri.path + } else if (isDocumentUri(ctx, uri)) { + + // Get uri related document id. + val documentId = DocumentsContract.getDocumentId(uri) + + // Get uri authority. + val uriAuthority: String? = uri.authority + if (isMediaDoc(uriAuthority)) { + val idArr = documentId.split(":").toTypedArray() + if (idArr.size == 2) { + // First item is document type. + val docType = idArr[0] + + // Second item is document real id. + val realDocId = idArr[1] + + // Get content uri by document type. + var mediaContentUri: Uri? = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + if ("image" == docType) { + mediaContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } else if ("video" == docType) { + mediaContentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } else if ("audio" == docType) { + mediaContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + // Get where clause with real document id. + val whereClause = MediaStore.Images.Media._ID + " = " + realDocId + ret = getImageRealPath(contentResolver, mediaContentUri, whereClause) + } + } else if (isDownloadDoc(uriAuthority)) { + // Build download uri. + val downloadUri: Uri = Uri.parse("content://downloads/public_downloads") + + // Append download document id at uri end. + val downloadUriAppendId: Uri = + ContentUris.withAppendedId(downloadUri, java.lang.Long.valueOf(documentId)) + ret = getImageRealPath(contentResolver, downloadUriAppendId, null) + } else if (isExternalStoreDoc(uriAuthority)) { + val idArr = documentId.split(":").toTypedArray() + if (idArr.size == 2) { + val type = idArr[0] + val realDocId = idArr[1] + if ("primary".equals(type, ignoreCase = true)) { + ret = Environment.getExternalStorageDirectory() + .toString() + "/" + realDocId + } + } + } + } + } + return ret + } + + /* Check whether this uri represent a document or not. */ + private fun isDocumentUri(ctx: Context?, uri: Uri?): Boolean { + var ret = false + if (ctx != null && uri != null) { + ret = DocumentsContract.isDocumentUri(ctx, uri) + } + return ret + } + + /* Check whether this uri is a content uri or not. + * content uri like content://media/external/images/media/1302716 + */ + private fun isContentUri(uri: Uri?): Boolean { + var ret = false + if (uri != null) { + val uriSchema: String? = uri.scheme + if ("content".equals(uriSchema, ignoreCase = true)) { + ret = true + } + } + return ret + } + + /* Check whether this uri is a file uri or not. + * file uri like file:///storage/41B7-12F1/DCIM/Camera/IMG_20180211_095139.jpg + */ + private fun isFileUri(uri: Uri?): Boolean { + var ret = false + if (uri != null) { + val uriSchema: String? = uri.scheme + if ("file".equals(uriSchema, ignoreCase = true)) { + ret = true + } + } + return ret + } + + + /* Check whether this document is provided by ExternalStorageProvider. Return true means the file is saved in external storage. */ + private fun isExternalStoreDoc(uriAuthority: String?): Boolean { + var ret = false + if (uriAuthority != null && "com.android.externalstorage.documents" == uriAuthority) { + ret = true + } + return ret + } + + /* Check whether this document is provided by DownloadsProvider. return true means this file is a downloaed file. */ + private fun isDownloadDoc(uriAuthority: String?): Boolean { + var ret = false + if (uriAuthority != null && "com.android.providers.downloads.documents" == uriAuthority) { + ret = true + } + return ret + } + + /* + * Check if MediaProvider provide this document, if true means this image is created in android media app. + */ + private fun isMediaDoc(uriAuthority: String?): Boolean { + var ret = false + if (uriAuthority != null && "com.android.providers.media.documents" == uriAuthority) { + ret = true + } + return ret + } + + /* + * Check whether google photos provide this document, if true means this image is created in google photos app. + */ + private fun isGooglePhotoDoc(uriAuthority: String?): Boolean { + var ret = false + if (uriAuthority != null && "com.google.android.apps.photos.content" == uriAuthority) { + ret = true + } + return ret + } + + /* Return uri represented document file real local path.*/ + private fun getImageRealPath( + contentResolver: ContentResolver, + uri: Uri?, + whereClause: String? + ): String { + var ret = "" + + if(uri != null) { + // Query the uri with condition. + val cursor: Cursor? = contentResolver.query(uri, null, whereClause, null, null) + if (cursor != null) { + val moveToFirst: Boolean = cursor.moveToFirst() + if (moveToFirst) { + + // Get columns name by uri type. + var columnName = MediaStore.Images.Media.DATA + if (uri === MediaStore.Images.Media.EXTERNAL_CONTENT_URI) { + columnName = MediaStore.Images.Media.DATA + } else if (uri === MediaStore.Audio.Media.EXTERNAL_CONTENT_URI) { + columnName = MediaStore.Audio.Media.DATA + } else if (uri === MediaStore.Video.Media.EXTERNAL_CONTENT_URI) { + columnName = MediaStore.Video.Media.DATA + } + + // Get column index. + val imageColumnIndex: Int = cursor.getColumnIndex(columnName) + + // Get column value which is the uri related file local path. + ret = cursor.getString(imageColumnIndex) + // Clean up + cursor.close() + } + } + } + return ret + } + + /* * Native methods that are implemented by the 'dwindows' native library, * which is packaged with this application. */