0

I want to record a video and then save it once it is done recording for 5 seconds. I have the following code which works fine on API < 24, however for API > 24 I get the error.

Code:

public void startRecording()
{
    File mediaFile = new
            File(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
            + "/myvideo.mp4");


    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,5);
    fileUri = Uri.fromFile(mediaFile);

    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
    startActivityForResult(intent, VIDEO_CAPTURE);
}

protected void onActivityResult(int requestCode,
                                int resultCode, Intent data) {

    if (requestCode == VIDEO_CAPTURE) {
        if (resultCode == RESULT_OK) {
            Toast.makeText(this, "Video has been saved to:\n" +
                    data.getData(), Toast.LENGTH_LONG).show();
        } else if (resultCode == RESULT_CANCELED) {
            Toast.makeText(this, "Video recording cancelled.",
                    Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(this, "Failed to record video",
                    Toast.LENGTH_LONG).show();
        }
    }
}

Error:

2019-10-08 01:15:43.483 21573-21573/com.mobilecomputing.learn2sign E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.mobilecomputing.learn2sign, PID: 21573
    android.os.FileUriExposedException: file:///storage/emulated/0/myvideo.mp4 exposed beyond app through ClipData.Item.getUri()
        at android.os.StrictMode.onFileUriExposed(StrictMode.java:1978)
        at android.net.Uri.checkFileUriExposed(Uri.java:2371)
        at android.content.ClipData.prepareToLeaveProcess(ClipData.java:963)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:10228)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:10213)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1854)
        at android.app.Activity.startActivityForResult(Activity.java:4599)
        at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:676)
        at android.app.Activity.startActivityForResult(Activity.java:4557)
        at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:663)
        at com.mobilecomputing.learn2sign.PlayHelpVideo.startRecording(PlayHelpVideo.java:125)
        at com.mobilecomputing.learn2sign.PlayHelpVideo$1.onClick(PlayHelpVideo.java:46)
        at android.view.View.performClick(View.java:6669)
        at android.view.View.performClickInternal(View.java:6638)
        at android.view.View.access$3100(View.java:789)
        at android.view.View$PerformClick.run(View.java:26145)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

I checked and the error is because of the Syntax of File object and that it is not supported on API > 24.

Although I could find other codes which were working I am curious as to if this code has a minor tweak which could make it work for API > 24 too. Perhaps something on the same line.

Can anyone help me on this ?

Edit:

I have tried: https://inthecheesefactory.com/blog/how-to-share-access-to-file-with-fileprovider-on-android-nougat/en but doing this makes application crash on API 22 as well.

Update:

Post making changes per one of the replied answers, code works on API < 24 and less without crashing properly. However on API > 24 although it doesnt crash, but createImageFile() function throws an exception:

private File createImageFile() throws IOException {
    // Create an image file name
    String imageFileName = "myvideo";
    File storageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DCIM), "Camera");
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".mp4",         /* suffix */
            storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    Log.v("myTag","FAB create image");
    mCurrentPhotoPath = "file:" + image.getAbsolutePath();
    return image;
}

This is called from the below function:

public void startRecording()
{

    Log.v("myTag","FAB recording");
    File mediaFile = null;
    try {
        mediaFile = createImageFile();
    } catch (IOException ex) {
        Log.v("myTag","Exception");
        return;
    }


    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,5);
    //fileUri = Uri.fromFile(mediaFile);
    fileUri = FileProvider.getUriForFile(PlayHelpVideo.this,
            BuildConfig.APPLICATION_ID + ".provider",
            mediaFile);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
    startActivityForResult(intent, VIDEO_CAPTURE);
}
R_Moose
  • 103
  • 9

1 Answers1

1

You can use FileProvider class to give access to the particular file or folder to make them accessible for other apps. Create your own class inheriting FileProvider in order to make sure your FileProvider doesn't conflict with FileProviders declared in imported dependencies as described here.

Steps to replace file:// URI with content:// URI:

Add a class extending FileProvider

public class GenericFileProvider extends FileProvider {}

Add a FileProvider <provider> tag in AndroidManifest.xml under <application> tag. Specify a unique authority for the android:authorities attribute to avoid conflicts, imported dependencies might specify ${applicationId}.provider and other commonly used authorities.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    <application
        ...
        <provider
            android:name=".GenericFileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>

Then create a provider_paths.xml file in res/xml folder. Folder may be needed to created if it doesn't exist. The content of the file is shown below. It describes that we would like to share access to the External Storage at root folder (path=".") with the name external_files.

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

The final step is to change the line of code below in

fileUri = Uri.fromFile(mediaFile);

to

fileUri= FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", mediaFile);
Jawad Malik
  • 608
  • 5
  • 21
  • post doing this my record button stops working on api > 24. Still works on emulator API < 24. This did help my app from crashing. However my fab button doesnt work or calls recording activity anymore – R_Moose Oct 08 '19 at 10:22