How to show more providers with ACTION_OPEN_DOCUMENT
Asked Answered
F

2

13

I want to use the android system dialog provided as part of the Storage Access Framework to open a file. I do this with

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/pdf");
startActivityForResult(intent, EDIT_REQUEST);

and then handle the returned URI in onActivityResult().

The problem is that, in the resulting menu, I get far less content providers than I expected. Only Google Drive and Downloads (see left screen shot below). Others, like Dropbox, Solid Explorer,... are not show.

I suspect the reason is that these apps simply don't set the necessary intent filter to show up in this list.

However, other apps, for example Kaiten Mail or Chrome, somehow manage to show the system dialog with fully implemented content providers at the top of the list and then others, like Dropbox and Solid Explorer, below, separated by a thin bar (see the right screen shot).

How can I get this behavior?

Comparison of the behavior I get (left) and the one I want (right)

Farland answered 19/12, 2014 at 14:33 Comment(5)
See what shows up in LogCat when you invoke Kaiten Mail's document chooser. Or, install App Browser and see what the other apps have in their <intent-filter> elements that may be missing in yours. Are you sure that this is "the system dialog", and not a workalike that is blending ACTION_OPEN_DOCUMENT and ACTION_GET_CONTENT results or something?Watersick
By looking into the source code of Chrom(ium) once can actually see that the behavior on the right hand side is produced by a relatively simple Intent.ACTION_GET_CONTENT. It is kind of sad to see that the originally good idea of the storage access framework seems to be so badly supported by virtually all apps that it is essentially useless...Farland
I think for example Dropbox must implement SAF. To my knowledge they don't support it.Crocoite
@cgogolin, did you find why the Dropbox (and other app) are not displayed in your implementation?Spital
They simply don't support the SAF.Farland
V
2

Use 'ACTION_GET_CONTENT:

Intent intent = new Intent(Intent. ACTION_GET_CONTENT);
intent.setType("application/pdf");
startActivityForResult(intent, EDIT_REQUEST);
Valerle answered 27/7, 2015 at 12:23 Comment(0)
S
0

We also had this issue. It seems there are multiple ways to get a chooser.

Here's what we did:

object ThirdPartyIntentsUtil {
        //    https://medium.com/@louis993546/how-to-ask-system-to-open-intent-to-select-jpg-and-png-only-on-android-i-e-no-gif-e0491af240bf
        //example usage: mainType= "*/*"  extraMimeTypes= arrayOf("image/*", "video/*") - choose all images and videos
        //example usage: mainType= "*/image"  extraMimeTypes= arrayOf("image/jpeg", "image/png") - choose all images of png and jpeg types
        /**note that this only requests to choose the files, but it's not guaranteed that this is what you will get*/
        @JvmStatic
        fun getPickFileIntent(context: Context, mainType: String = "*/*", extraMimeTypes: Array<String>? = null): Intent? {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
                return null
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            intent.type = mainType
            if (!extraMimeTypes.isNullOrEmpty())
                intent.putExtra(Intent.EXTRA_MIME_TYPES, extraMimeTypes)
            if (context.packageManager.queryIntentActivities(intent, 0).isNullOrEmpty())
                return null
            return intent
        }

        //    https://github.com/linchaolong/ImagePicker/blob/master/library/src/main/java/com/linchaolong/android/imagepicker/cropper/CropImage.java
        @JvmStatic
        fun getPickFileChooserIntent(
                context: Context, title: CharSequence?, preferDocuments: Boolean = true,includeCameraIntents:Boolean, mainType: String
                , extraMimeTypes: Array<String>? = null, extraIntents: ArrayList<Intent>? = null
        ): Intent? {
            val packageManager = context.packageManager
            var allIntents =
                    getGalleryIntents(packageManager, Intent.ACTION_GET_CONTENT, mainType, extraMimeTypes)
            if (allIntents.isEmpty()) {
                // if no intents found for get-content try pick intent action (Huawei P9).
                allIntents =
                        getGalleryIntents(packageManager, Intent.ACTION_PICK, mainType, extraMimeTypes)
            }
            val cameraIntents = getCameraIntents(packageManager)
            allIntents.addAll(0, cameraIntents)
    //        Log.d("AppLog", "got ${allIntents.size} intents")
            if (allIntents.isEmpty())
                return null
            if (preferDocuments)
                for (intent in allIntents)
                    if (intent.component!!.packageName == "com.android.documentsui")
                        return intent
            if (allIntents.size == 1)
                return allIntents[0]
            var target: Intent? = null
            for ((index, intent) in allIntents.withIndex()) {
                if (intent.component!!.packageName == "com.android.documentsui") {
                    target = intent
                    allIntents.removeAt(index)
                    break
                }
            }
            if (target == null)
                target = allIntents[allIntents.size - 1]
            allIntents.removeAt(allIntents.size - 1)
            // Create a chooser from the main  intent
            val chooserIntent = Intent.createChooser(target, title)
            if (extraIntents != null && extraIntents.isNotEmpty())
                allIntents.addAll(extraIntents)
            // Add all other intents
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, allIntents.toTypedArray<Parcelable>())
            return chooserIntent
        }

        private fun getCameraIntents(packageManager: PackageManager): ArrayList<Intent> {
            val cameraIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
            val listCamera = packageManager.queryIntentActivities(cameraIntent, 0)
            val intents = ArrayList<Intent>()
            for (res in listCamera) {
                val intent = Intent(cameraIntent)
                intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
                intent.`package` = res.activityInfo.packageName
                intents.add(intent)
            }
            return intents
        }

        /**
         * Get all Gallery intents for getting image from one of the apps of the device that handle
         * images. Intent.ACTION_GET_CONTENT and then Intent.ACTION_PICK
         */
        @TargetApi(Build.VERSION_CODES.KITKAT)
        private fun getGalleryIntents(
                packageManager: PackageManager, action: String,
                mainType: String , extraMimeTypes: Array<String>? = null
        ): ArrayList<Intent> {
            val galleryIntent = if (action == Intent.ACTION_GET_CONTENT)
                Intent(action)
            else
                Intent(action, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            galleryIntent.type = mainType
            if (!extraMimeTypes.isNullOrEmpty()) {
                galleryIntent.addCategory(Intent.CATEGORY_OPENABLE)
                galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, extraMimeTypes)
            }
            val listGallery = packageManager.queryIntentActivities(galleryIntent, 0)
            val intents = ArrayList<Intent>()
            for (res in listGallery) {
                val intent = Intent(galleryIntent)
                intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
                intent.`package` = res.activityInfo.packageName
                intents.add(intent)
            }
            return intents
        }

        @JvmStatic
        fun getMimeType(context: Context, uri: Uri): String? {
            val mimeType: String? = if (ContentResolver.SCHEME_CONTENT == uri.scheme) {
                val cr = context.contentResolver
                cr.getType(uri)
            } else {
                val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
                MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase())
            }
            return mimeType
        }

    }

So, suppose you wish to choose a video file, you can do as such:

private val mimeTypeMap = MimeTypeMap.getSingleton()
private val videosMimeTypes = arrayOf(mimeTypeMap.getMimeTypeFromExtension("mkv"), mimeTypeMap.getMimeTypeFromExtension("mp4"), mimeTypeMap.getMimeTypeFromExtension("3gp"))

...
val intentForChoosingVideos = ThirdPartyIntentsUtil .getPickFileChooserIntent(this,null,
            true, "videos/*", videosMimeTypes)
            ?: getPickFileIntent(this, "video/*,", videosMimeTypes)

You can change it to handle other file types of course. Here's for image files:

private val mimeTypeMap = MimeTypeMap.getSingleton()
private val imagesMimeTypes = arrayOf(mimeTypeMap.getMimeTypeFromExtension("png"), mimeTypeMap.getMimeTypeFromExtension("jpg"), mimeTypeMap.getMimeTypeFromExtension("webp"))

...
val intentForChoosingImages = ThirdPartyIntentsUtil .getPickFileChooserIntent(this, null,
            true, "image/*", imagesMimeTypes)
            ?: getPickFileIntent(this, "image/*,", imagesMimeTypes)

It will try to have the extended one, and if it fails, it will try to get a chooser of which app to use. If that fails, it will return null.

Note that for some reason, "Google Photos" app doesn't allow to choose just videos, so if you want to include it, use a more generic form:

    val intentForChoosingVideos = ThirdPartyIntentsUtil.getPickFileChooserIntent(this, null,
            false, true,"*/*", videosMimeTypes)
            ?: ThirdPartyIntentsUtil.getPickFileIntent(this, "video/*,", videosMimeTypes)
Skep answered 3/2, 2019 at 11:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.