I have a file in my raw assets directory which I am trying to save to shared phone storage that will allow other apps to open and view this video.
I am able to save the file to my local app storage or to external private app storage using the following code.
Intent in = new Intent(Intent.ACTION_VIEW);
// using getFilesDir() below causes this error:
File videoFile = new File(getExternalFilesDirs(null)[1], "talking.mp4");
// check if we have already copied file over
if (!videoFile.exists()) {
try {
InputStream is = getResources().openRawResource(R.raw.talking);
boolean newFile = videoFile.createNewFile();
if (!newFile) {
throw new Exception("Failed to create file");
}
OutputStream os = new FileOutputStream(videoFile);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
is.close();
os.close();
} catch (IOException e) {
Log.w("ExternalStorage", "Error writing " + videoFile, e);
Toast.makeText(this, "Failed to copy talking.mp4", Toast.LENGTH_LONG).show();
return;
} catch (Exception e) {
e.printStackTrace();
}
}
// Line below 135
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName(), videoFile);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Problematic Line
// type of file to view
in.setDataAndType(uri, "video/*");
// ask some other app to deal with it
startActivity(in);
The problem is I keep getting this error:
Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains
/storage/1B12-3A08/Android/data/${getPackageName()}/files/talking.mp4
at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:744)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:418)
at ${getPackageName()}.MainActivity.btnPlayIntentClicked(MainActivity.java:135)
Having done a quick bit of research with the help of this link and FileProvider, I added this to my Manifest:
<application ... >
//...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${getPackageName()}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/paths" />
</provider>
</application>
and have the following resource at res/xml/paths.xml
:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_files" path="." />
<external-files-path name="my_files" path="." />
<external-path name="my_files" path="." />
</paths>
Question:
Having configured the available content root, I should be able to copy over a file from raw resources to external private storage and request Android (via implicit intent) to play the video using e.g. MX Player or VLC for Android.
What am I missing with the content root configuration? (I added both external-files-path
and external-path
for good measure)
Update after @Commonsware's answer
I changed
File videoFile = new File(getExternalFilesDirs(null)[1], "talking.mp4");
to
File videoFile = new File(getExternalFilesDir(null), "talking.mp4");
which is also just
File videoFile = new File(getExternalFilesDirs(null)[0], "talking.mp4");
But this gave the same error, but just another path prefix to the file:
Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/${getPackageName()}/files/talking.mp4
I was able to get the file the content root to work and pass the file Uri with an Intent, which gave the option of open with VLC (for Android) and a few others, problem was non of the apps could play the file. I cannot remember how I did it though
Update 2
@BlackApps's suggestion of changing the path names in the res/xml/paths.xml
file solved the problem
Initially I had different names, but it had not worked and didn't consider changing them back after some experimentation.
Reason why it caused the problem:
Content Root paths are loaded on app start via this function:
FileProvider.parsePathStrategy(Context, String)
Inside, it create a cache (Map) of all paths by using their corresponding file provider paths to provide additional context (appending a path to the specified path in the <external-file/>
,<files-path/>
. Given the input:
<external-file name="some_name" path="MyAwesomeAssets"/>
will result in an (external) target path (of Environment.getExternalStorageDirectory()
) prepended to the path "MyAweomseAssets" resulting in a output path of /storage/emulated/0/MyAwesomeAssets
.
This is then added to a cache, but in my case since I had the same name for each, it would simply override the last path i.e. FIFO approach.
So, when I am attempting to get the Uri of a file in the content roots I specified, there will only be one found, and that would be the last one defined in res/xml/paths.xml
.