How to properly let the user pick videos and images (targetSdkVersion 30)

Viewed 110

This is a somewhat broad question that covers a few topics that are all related to the same goal: Understanding how to properly load multimedia content e.g. videos and images with the standard Android "picker"/"gallery", not using any third party library.

Before I upgraded to targetSdkVersion 30, this code worked properly on every known device:

val intent = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
intent.type = "video/*"
intent.action = Intent.ACTION_GET_CONTENT
intent.putExtra(
    MediaStore.EXTRA_OUTPUT,
    Uri.fromFile(File(filesDir, "chosenVideo"))
)

if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
    requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PICK_VIDEO_PERMISSION)
} else {
    startActivityForResult(Intent.createChooser(intent, "Select Video"), PICK_VIDEO)
}

However, not only does this not work on some devices, startActivityForResult is also deprecated.

The result is processed like this:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    //...
    val dataSource = PathUtils.getPath(this, data!!.data) /* Code for getPath further blow */

dataSource is the file path in form of a string and passed to the next activity, where the error occurs:

override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
        Log.d("VIDEOPLAYER", "SURFACE")
        mediaPlayer.setSurface(Surface(surface))
        mediaPlayer.isLooping = true
        mediaPlayer.setDataSource(fullFilePath) // line 428

Sidenote about the error: The error occurred on e.g. a Samsung Galaxy S9, but sadly at a somewhat unrelated part of the code, where I basically try to load the chosen video and it crashes because I am not allowed to. Can't reproduce the error on my device, so I have yet to debug it properly, but from what it looks like, it's a permission issue. But as I said, it works on my Pixel 4a, but not on a Samsung Galaxy S9, which is odd.

Screenshot from Firebase:

enter image description here

Full error:

libcore.io.Linux.open (Linux.java)
libcore.io.ForwardingOs.open (ForwardingOs.java:167)
libcore.io.BlockGuardOs.open (BlockGuardOs.java:252)
libcore.io.ForwardingOs.open (ForwardingOs.java:167)
android.app.ActivityThread$AndroidOs.open (ActivityThread.java:8044)
libcore.io.IoBridge.open (IoBridge.java:482)
java.io.FileInputStream.<init> (FileInputStream.java:159)
android.media.MediaPlayer.setDataSource (MediaPlayer.java:1268)
android.media.MediaPlayer.setDataSource (MediaPlayer.java:1239)
android.media.MediaPlayer.setDataSource (MediaPlayer.java:1204)
com.mycompany.app.camera.VideoEditActivity.onSurfaceTextureAvailable (VideoEditActivity.kt:428)
android.view.TextureView.getTextureLayer (TextureView.java:400)
android.view.TextureView.draw (TextureView.java:349)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw (ConstraintLayout.java:1994)
android.view.View.draw (View.java:23190)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw (ConstraintLayout.java:1994)
android.view.View.draw (View.java:23190)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.draw (View.java:23190)
com.android.internal.policy.DecorView.draw (DecorView.java:1154)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.ThreadedRenderer.updateViewTreeDisplayList (ThreadedRenderer.java:588)
android.view.ThreadedRenderer.updateRootDisplayList (ThreadedRenderer.java:594)
android.view.ThreadedRenderer.draw (ThreadedRenderer.java:667)
android.view.ViewRootImpl.draw (ViewRootImpl.java:4293)
android.view.ViewRootImpl.performDraw (ViewRootImpl.java:4077)
android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:3345)
android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:2222)
android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:9123)
android.view.Choreographer$CallbackRecord.run (Choreographer.java:999)
android.view.Choreographer.doCallbacks (Choreographer.java:797)
android.view.Choreographer.doFrame (Choreographer.java:732)
android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:984)
android.os.Handler.handleCallback (Handler.java:883)
android.os.Handler.dispatchMessage (Handler.java:100)
android.os.Looper.loop (Looper.java:237)
android.app.ActivityThread.main (ActivityThread.java:8167)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:496)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1100)

So, my question is: is there a "right" way to let a person pick a video or image and get the file on all devices, without any permission issues.

I looked at some documentations:
https://developer.android.com/reference/android/database/Cursor https://developer.android.com/reference/androidx/loader/content/CursorLoader https://developer.android.com/reference/android/provider/MediaStore.Images.Media.html https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts
And some more.

In addition to that, I am using code that I took from other SO questions, which I can't find at the moment, like the one above to let the user pick an image/video and this one I generically use and which I just copy-pasted to get the file in the format I need it:

public class PathUtils {

    public static String getPath(final Context context, final Uri uri)
    {
        // DocumentProvider
        if (DocumentsContract.isDocumentUri(context, uri)) {

            if (isDownloadsDocument(uri)) {// DownloadsProvider

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)
                );

                return getDataColumn(context, contentUri, null, null);

            } else if (isMediaDocument(uri)) {// MediaProvider
                final String docId = DocumentsContract.getDocumentId(uri);
                final String [] split = docId . split (":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String [] selectionArgs = new String[]{
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }

        } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general)

            // Return the remote address
            if (isGooglePhotosUri(uri))
                return uri.getLastPathSegment();

            return getDataColumn(context, uri, null, null);

        } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File
            return uri.getPath();
        }

        return null;
    }

    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs)
    {

        Cursor cursor = null;
        final String column = "_data";
        final String [] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    public static boolean isDownloadsDocument(Uri uri)
    {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    public static boolean isMediaDocument(Uri uri)
    {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    public static boolean isGooglePhotosUri(Uri uri)
    {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }
}

Which I quite honestly don't fully understand.

Please note: I am not looking for "opinions" about what is better or worse. I am looking for specific flaws in my approach and a reliable and correct way of doing this, that is supported with good arguments, references to documentations and official statements.

0 Answers
Related