Create and Share a File from Internal Storage
If you need to permission other apps to see your app's private files (for Share, or otherwise) you might be able to save some time and just use v4 compat library's FileProvider
It is possible to expose a file stored in your apps private directory via a ContentProvider. Here is some example code I made showing how to create a content provider that can do this.
Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.providertest"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="15" />
<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="MyProvider"
android:authorities="com.example.prov"
android:exported="true"
/>
</application>
</manifest>
In your ContentProvider override openFile to return the ParcelFileDescriptor
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File cacheDir = getContext().getCacheDir();
File privateFile = new File(cacheDir, "file.xml");
return ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY);
}
Make sure you have copied your xml file to the cache directory
private void copyFileToInternal() {
try {
InputStream is = getAssets().open("file.xml");
File cacheDir = getCacheDir();
File outFile = new File(cacheDir, "file.xml");
OutputStream os = new FileOutputStream(outFile.getAbsolutePath());
byte[] buff = new byte[1024];
int len;
while ((len = is.read(buff)) > 0) {
os.write(buff, 0, len);
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace(); // TODO: should close streams properly here
}
}
Now any other apps should be able to get an InputStream for your private file by using the content uri (content://com.example.prov/myfile.xml)
For a simple test, call the content provider from a seperate app similar to the following
private class MyTask extends AsyncTask<String, Integer, String> {
@Override
protected String doInBackground(String... params) {
Uri uri = Uri.parse("content://com.example.prov/myfile.xml");
InputStream is = null;
StringBuilder result = new StringBuilder();
try {
is = getApplicationContext().getContentResolver().openInputStream(uri);
BufferedReader r = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = r.readLine()) != null) {
result.append(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (is != null) is.close(); } catch (IOException e) { }
}
return result.toString();
}
@Override
protected void onPostExecute(String result) {
Toast.makeText(CallerActivity.this, result, Toast.LENGTH_LONG).show();
super.onPostExecute(result);
}
}
None of the above answers helped. My problem was the point of passing intent extras but I'll walk you through all the steps to share a file.
Step 1: Create a Content Provider
This will make the file accessible to whichever app you want to share with.
Paste the following in the Manifest.xml file inside the <application></applicatio>
tags
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="{YOUR_PACKAGE_NAME}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
Step 2: Define paths accessible by the content provider Do this by creating a file called provider_paths.xml (or a name of your choice) under res/xml. Put the following code in the file
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
Step 3: Create the Intent to share the file
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".fileprovider", fileToShare);
intentShareFile.setDataAndType(uri, URLConnection.guessContentTypeFromName(fileToShare.getName()));
//Allow sharing apps to read the file Uri
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//Pass the file Uri instead of the path
intentShareFile.putExtra(Intent.EXTRA_STREAM,
uri);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
So Rob's answer is correct I assume but I did it a bit differently. As far as I understand, with the setting in in provider:
android:exported="true"
you are giving public access to all your files?! Anyway, a way to give only access to some files is to define file path permissions in the following way:
<provider
android:authorities="com.your.app.package"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
and then in your XML directory you define file_paths.xml file as follows:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="/" name="allfiles" />
<files-path path="tmp/" name="tmp" />
</paths>
now, the "allfiles" gives the same kind of public permission I guess as the option android:exported="true" but you don't really want that I guess so to define a subdirectory is the next line. Then all you have to do is store the files you want to share, there in that dir.
Next what you have to do is, as also Rob says, obtain a URI for this file. This is how I did it:
Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile);
Then, when I have this URI, I had to attach to it permissions for other app to use it. I was using or sending this file URI to camera app. Anyway this is the way how I got the other app package info and granted permissions to the URI:
PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() < 1) {
return;
}
String packageName = list.get(0).activityInfo.packageName;
grantUriPermission(packageName, sharedFileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
ClipData clipData = ClipData.newRawUri("CAMFILE", sharedFileUri);
cameraIntent.setClipData(clipData);
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cameraIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(cameraIntent, GET_FROM_CAMERA);
I left the code for camera as I did not want to take some other example I did not work on. But this way you see that you can attach permissions to URI itself.
The camera's thing is that I can set it via ClipData and then additionally set permissions. I guess in your case you only need FLAG_GRANT_READ_URI_PERMISSION as you are attaching a file to an email.
Here is the link to help on FileProvider as I based all of my post on the info I found there. Had some trouble finding a package info for camera app though.
Hope it helps.