comparison android/DWindows.kt @ 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 8352c38bc20b
children 6b5057dd6b8e
comparison
equal deleted inserted replaced
2593:cc2befdc97e8 2594:2c15b3d41fe4
3 import android.R 3 import android.R
4 import android.app.Activity 4 import android.app.Activity
5 import android.app.Dialog 5 import android.app.Dialog
6 import android.app.NotificationChannel 6 import android.app.NotificationChannel
7 import android.app.NotificationManager 7 import android.app.NotificationManager
8 import android.content.ClipData 8 import android.content.*
9 import android.content.ClipboardManager
10 import android.content.Context
11 import android.content.DialogInterface
12 import android.content.pm.ActivityInfo 9 import android.content.pm.ActivityInfo
13 import android.content.res.Configuration 10 import android.content.res.Configuration
11 import android.database.Cursor
14 import android.graphics.* 12 import android.graphics.*
15 import android.graphics.drawable.BitmapDrawable 13 import android.graphics.drawable.BitmapDrawable
16 import android.graphics.drawable.Drawable 14 import android.graphics.drawable.Drawable
17 import android.graphics.drawable.GradientDrawable 15 import android.graphics.drawable.GradientDrawable
18 import android.media.AudioManager 16 import android.media.AudioManager
19 import android.media.ToneGenerator 17 import android.media.ToneGenerator
18 import android.net.Uri
20 import android.os.* 19 import android.os.*
20 import android.provider.DocumentsContract
21 import android.provider.MediaStore
21 import android.text.InputFilter 22 import android.text.InputFilter
22 import android.text.InputFilter.LengthFilter 23 import android.text.InputFilter.LengthFilter
23 import android.text.InputType 24 import android.text.InputType
24 import android.text.method.PasswordTransformationMethod 25 import android.text.method.PasswordTransformationMethod
25 import android.util.Base64 26 import android.util.Base64
738 var darkMode: Int = -1 739 var darkMode: Int = -1
739 private var paint = Paint() 740 private var paint = Paint()
740 private var bgcolor: Int = 0 741 private var bgcolor: Int = 0
741 private var menuBar: DWMenu? = null 742 private var menuBar: DWMenu? = null
742 private var defaultItem: View? = null 743 private var defaultItem: View? = null
744 private var fileURI: Uri? = null
743 745
744 // Our version of runOnUiThread that waits for execution 746 // Our version of runOnUiThread that waits for execution
745 fun waitOnUiThread(runnable: Runnable) 747 fun waitOnUiThread(runnable: Runnable)
746 { 748 {
747 if(Looper.myLooper() == Looper.getMainLooper()) { 749 if(Looper.myLooper() == Looper.getMainLooper()) {
3219 fun debugMessage(text: String) 3221 fun debugMessage(text: String)
3220 { 3222 {
3221 Log.d(null, text) 3223 Log.d(null, text)
3222 } 3224 }
3223 3225
3226 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
3227 super.onActivityResult(requestCode, resultCode, data)
3228 if(requestCode == 100 && resultCode == Activity.RESULT_OK) {
3229 fileURI = data!!.data
3230 throw java.lang.RuntimeException()
3231 }
3232 }
3233
3234 fun fileBrowseNew(title: String, defpath: String?, ext: String?, flags: Int): String?
3235 {
3236 var retval: String? = null
3237
3238 waitOnUiThread {
3239 val fileintent = Intent(Intent.ACTION_GET_CONTENT)
3240 fileintent.type = "text/plain"
3241 fileintent.addCategory(Intent.CATEGORY_OPENABLE)
3242 startActivityForResult(fileintent, 100)
3243 }
3244
3245 // loop till a runtime exception is triggered.
3246 try {
3247 Looper.loop()
3248 } catch (e2: RuntimeException) {
3249 }
3250
3251 retval = getUriRealPath(this, fileURI)
3252 return retval
3253 }
3254
3224 fun fileBrowse(title: String, defpath: String?, ext: String?, flags: Int): String? 3255 fun fileBrowse(title: String, defpath: String?, ext: String?, flags: Int): String?
3225 { 3256 {
3226 var retval: String? = null 3257 var retval: String? = null
3227 3258
3228 waitOnUiThread { 3259 waitOnUiThread {
3229 val fc = DWFileChooser(this) 3260 val fc = DWFileChooser(this)
3230 fc.setFileListener(object: DWFileChooser.FileSelectedListener { 3261 fc.setFileListener(object: DWFileChooser.FileSelectedListener {
3231 override fun fileSelected(file: File?) { 3262 override fun fileSelected(file: File?) {
3232 // do something with the file 3263 // do something with the file
3233 retval = file!!.absolutePath 3264 retval = file!!.absolutePath
3234 throw java.lang.RuntimeException() 3265 throw java.lang.RuntimeException()
3235 } 3266 }
3236 }) 3267 })
3237 if(ext != null) { 3268 if(ext != null) {
3238 fc.setExtension(ext) 3269 fc.setExtension(ext)
3239 } 3270 }
3240 fc.showDialog() 3271 fc.showDialog()
3241 } 3272 }
3416 with(NotificationManagerCompat.from(this)) { 3447 with(NotificationManagerCompat.from(this)) {
3417 // notificationId is a unique int for each notification that you must define 3448 // notificationId is a unique int for each notification that you must define
3418 notify(notificationID, builder.build()) 3449 notify(notificationID, builder.build())
3419 } 3450 }
3420 } 3451 }
3452 }
3453
3454 /*
3455 * This method will parse out the real local file path from the file content URI.
3456 */
3457 private fun getUriRealPath(ctx: Context?, uri: Uri?): String? {
3458 var ret: String? = ""
3459 if (ctx != null && uri != null) {
3460 if (isContentUri(uri)) {
3461 if (isGooglePhotoDoc(uri.authority)) {
3462 ret = uri.lastPathSegment
3463 } else {
3464 ret = getImageRealPath(contentResolver, uri, null)
3465 }
3466 } else if (isFileUri(uri)) {
3467 ret = uri.path
3468 } else if (isDocumentUri(ctx, uri)) {
3469
3470 // Get uri related document id.
3471 val documentId = DocumentsContract.getDocumentId(uri)
3472
3473 // Get uri authority.
3474 val uriAuthority: String? = uri.authority
3475 if (isMediaDoc(uriAuthority)) {
3476 val idArr = documentId.split(":").toTypedArray()
3477 if (idArr.size == 2) {
3478 // First item is document type.
3479 val docType = idArr[0]
3480
3481 // Second item is document real id.
3482 val realDocId = idArr[1]
3483
3484 // Get content uri by document type.
3485 var mediaContentUri: Uri? = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
3486 if ("image" == docType) {
3487 mediaContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
3488 } else if ("video" == docType) {
3489 mediaContentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
3490 } else if ("audio" == docType) {
3491 mediaContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
3492 }
3493
3494 // Get where clause with real document id.
3495 val whereClause = MediaStore.Images.Media._ID + " = " + realDocId
3496 ret = getImageRealPath(contentResolver, mediaContentUri, whereClause)
3497 }
3498 } else if (isDownloadDoc(uriAuthority)) {
3499 // Build download uri.
3500 val downloadUri: Uri = Uri.parse("content://downloads/public_downloads")
3501
3502 // Append download document id at uri end.
3503 val downloadUriAppendId: Uri =
3504 ContentUris.withAppendedId(downloadUri, java.lang.Long.valueOf(documentId))
3505 ret = getImageRealPath(contentResolver, downloadUriAppendId, null)
3506 } else if (isExternalStoreDoc(uriAuthority)) {
3507 val idArr = documentId.split(":").toTypedArray()
3508 if (idArr.size == 2) {
3509 val type = idArr[0]
3510 val realDocId = idArr[1]
3511 if ("primary".equals(type, ignoreCase = true)) {
3512 ret = Environment.getExternalStorageDirectory()
3513 .toString() + "/" + realDocId
3514 }
3515 }
3516 }
3517 }
3518 }
3519 return ret
3520 }
3521
3522 /* Check whether this uri represent a document or not. */
3523 private fun isDocumentUri(ctx: Context?, uri: Uri?): Boolean {
3524 var ret = false
3525 if (ctx != null && uri != null) {
3526 ret = DocumentsContract.isDocumentUri(ctx, uri)
3527 }
3528 return ret
3529 }
3530
3531 /* Check whether this uri is a content uri or not.
3532 * content uri like content://media/external/images/media/1302716
3533 */
3534 private fun isContentUri(uri: Uri?): Boolean {
3535 var ret = false
3536 if (uri != null) {
3537 val uriSchema: String? = uri.scheme
3538 if ("content".equals(uriSchema, ignoreCase = true)) {
3539 ret = true
3540 }
3541 }
3542 return ret
3543 }
3544
3545 /* Check whether this uri is a file uri or not.
3546 * file uri like file:///storage/41B7-12F1/DCIM/Camera/IMG_20180211_095139.jpg
3547 */
3548 private fun isFileUri(uri: Uri?): Boolean {
3549 var ret = false
3550 if (uri != null) {
3551 val uriSchema: String? = uri.scheme
3552 if ("file".equals(uriSchema, ignoreCase = true)) {
3553 ret = true
3554 }
3555 }
3556 return ret
3557 }
3558
3559
3560 /* Check whether this document is provided by ExternalStorageProvider. Return true means the file is saved in external storage. */
3561 private fun isExternalStoreDoc(uriAuthority: String?): Boolean {
3562 var ret = false
3563 if (uriAuthority != null && "com.android.externalstorage.documents" == uriAuthority) {
3564 ret = true
3565 }
3566 return ret
3567 }
3568
3569 /* Check whether this document is provided by DownloadsProvider. return true means this file is a downloaed file. */
3570 private fun isDownloadDoc(uriAuthority: String?): Boolean {
3571 var ret = false
3572 if (uriAuthority != null && "com.android.providers.downloads.documents" == uriAuthority) {
3573 ret = true
3574 }
3575 return ret
3576 }
3577
3578 /*
3579 * Check if MediaProvider provide this document, if true means this image is created in android media app.
3580 */
3581 private fun isMediaDoc(uriAuthority: String?): Boolean {
3582 var ret = false
3583 if (uriAuthority != null && "com.android.providers.media.documents" == uriAuthority) {
3584 ret = true
3585 }
3586 return ret
3587 }
3588
3589 /*
3590 * Check whether google photos provide this document, if true means this image is created in google photos app.
3591 */
3592 private fun isGooglePhotoDoc(uriAuthority: String?): Boolean {
3593 var ret = false
3594 if (uriAuthority != null && "com.google.android.apps.photos.content" == uriAuthority) {
3595 ret = true
3596 }
3597 return ret
3598 }
3599
3600 /* Return uri represented document file real local path.*/
3601 private fun getImageRealPath(
3602 contentResolver: ContentResolver,
3603 uri: Uri?,
3604 whereClause: String?
3605 ): String {
3606 var ret = ""
3607
3608 if(uri != null) {
3609 // Query the uri with condition.
3610 val cursor: Cursor? = contentResolver.query(uri, null, whereClause, null, null)
3611 if (cursor != null) {
3612 val moveToFirst: Boolean = cursor.moveToFirst()
3613 if (moveToFirst) {
3614
3615 // Get columns name by uri type.
3616 var columnName = MediaStore.Images.Media.DATA
3617 if (uri === MediaStore.Images.Media.EXTERNAL_CONTENT_URI) {
3618 columnName = MediaStore.Images.Media.DATA
3619 } else if (uri === MediaStore.Audio.Media.EXTERNAL_CONTENT_URI) {
3620 columnName = MediaStore.Audio.Media.DATA
3621 } else if (uri === MediaStore.Video.Media.EXTERNAL_CONTENT_URI) {
3622 columnName = MediaStore.Video.Media.DATA
3623 }
3624
3625 // Get column index.
3626 val imageColumnIndex: Int = cursor.getColumnIndex(columnName)
3627
3628 // Get column value which is the uri related file local path.
3629 ret = cursor.getString(imageColumnIndex)
3630 // Clean up
3631 cursor.close()
3632 }
3633 }
3634 }
3635 return ret
3421 } 3636 }
3422 3637
3423 /* 3638 /*
3424 * Native methods that are implemented by the 'dwindows' native library, 3639 * Native methods that are implemented by the 'dwindows' native library,
3425 * which is packaged with this application. 3640 * which is packaged with this application.