changeset 2594:2c15b3d41fe4

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.
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Fri, 28 May 2021 01:02:07 +0000
parents cc2befdc97e8
children 6b5057dd6b8e
files android/DWindows.kt
diffstat 1 files changed, 225 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- 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.
      */