I could not find a smart solution to my problem and that is why I decided to write to you good people. I have a question for your regarding FileProvider and ACTION_VIEW, namely, I save the html file this way:
File sdcard = getBaseContext().getExternalFilesDir(null);
File dir = new File(sdcard.getAbsolutePath() + “/MYDIR/“);
dir.mkdir();
File file = new File(dir, “MYFILE.html");
File recordFile = new File(sdcard.getAbsolutePath()+ "/MYDIR/" + "MYFILE.html");
FileOutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(returnedText.getText().toString().getBytes());
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
its path: storage/emulated/0/Android/data/com.myapp/files/MYDIR/MYFILE.html
This is how I call my html file:
File sdcard = getBaseContext().getExternalFilesDir(null);
Intent intent = new Intent(Intent.ACTION_VIEW);
File f = new File(sdcard.getAbsolutePath() + "/MYDIR" + "/MYFILE.html");
intent.setDataAndType(Uri.fromFile(f), "text/html");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|
Intent.FLAG_ACTIVITY_NO_HISTORY|
Intent.FLAG_ACTIVITY_NEW_TASK);
Intent.createChooser(intent, "File Browser");
startActivity(intent);
I want to call this MYFILE.html via Intent and open in HTML Viewer, I get this error:
ERR_ACCESS_DENIED
So I decided to try FileProvider, but I get a completely different path. What should I do, any specific idea??? Thanks in advance.
My FileProvider:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.myapp.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
My application has read and write permissions to the download folder. How to call the default application to open a file and give it the right to read the file?
When use this code, the default application opens, but cannot access the file:
public void openFile(String fileName)
{
String mimeType = URLConnection.guessContentTypeFromName(fileName);
Intent newIntent = new Intent(Intent.ACTION_VIEW);
Uri myUri = Uri.parse(fileName);
newIntent.setDataAndType(myUri, mimeType);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
newIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
try {
this.startActivity(newIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, "No handler for this type of file.", Toast.LENGTH_LONG).show();
}
}
Acrobat reader, for example, reports that there is no access to the file.
Only the GoogleFoto app when opened asked permission and successfully opened the picture.
Thanks to CommonsWare
android/AndroidManifest.xml
<!-- file provider for open attached files-->
<provider android:name="android.support.v4.content.FileProvider" android:authorities="XXX.XXXXXXXXXXXX" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/file_provider_paths"/>
</provider>
android/res/xml/file_provider_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="/" />
<files-path name="files" path="/" />
</paths>
MainActivity.java
public String openFile(String filePath)
{
File file = new File(filePath);
if (!file.exists())
return "file not exist";
Uri uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);
String mimeType = URLConnection.guessContentTypeFromName(filePath);
Intent newIntent = new Intent(Intent.ACTION_VIEW);
newIntent.setDataAndType(uri, mimeType);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
newIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
try {
this.startActivity(newIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, "No handler for this type of file.", Toast.LENGTH_LONG).show();
return "No handler for this type of file.";
}
return "";
}
I am creating a app in which i am generating pdf and after this app showing PDF automatically. it is working wee in 6.1 android but not showing in android 7.1. my code is
public void showPDF() {
File pdfFile = new File(Environment.getExternalStorageDirectory() , "A_DailyRegiser.pdf");
try {
if (pdfFile.exists()) {
Uri path = Uri.fromFile(pdfFile);
Intent objIntent = new Intent(Intent.ACTION_VIEW);
objIntent.setDataAndType(path, "application/pdf");
// objIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
objIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
objIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(objIntent);
} else {
Toast.makeText(dailycollection.this, "File NotFound",Toast.LENGTH_SHORT).show();
}
} catch (ActivityNotFoundException e) {
Toast.makeText(dailycollection.this,
"No Viewer Application Found", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
That's because of the new changes in Noughat API's Read more here
you are suggested to use the content:// scheme instead of file:// for this we can use the file provider API.
here is sample code:
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
...
</application>
define the scope of your access
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
Generating the Content URI for a File
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
Granting Temporary Permissions to a URI
Intent.setFlags() with either FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION or both.
read more here
I get this error: failed to find configured root that contains /storage/emulated/0/android/data/
There are some similar questions but they are using other location for storing files.
I am using:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) method and this is the code I'm using to open camera:
private void TakePicture() {
photoFile = FileManager.generateFileStampedPhotoFile();
PicturesHelper.TryTakePictureWithAnIntent(this, photoFile, REQUEST_IMAGE_CAPTURE);
}
public static File generateFileStampedPhotoFile()
{
Uri photoFileUri=null;
File photoFile=null;
File outputDir=getPhotoDirectory();
if(outputDir!=null)
{
String timeStamp=new SimpleDateFormat("yyyyMMDD_HHmmss").format(new Date());
String photoFileName="IMG_"+timeStamp+".jpg";
photoFile=new File(outputDir,photoFileName);
// photoFileUri=Uri.fromFile(photoFile);
}
return photoFile;
}
public static void TryTakePictureWithAnIntent(Activity context,File
photoFile,int requestCode)
{
Intent takePictureIntent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if(takePictureIntent.resolveActivity(context.getPackageManager())!=null) //if user has camera?
{
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(context,
"com.myapp.fileprovider",
photoFile);
takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
context.startActivityForResult(takePictureIntent, requestCode);
}
}
}
public static File getPhotoDirectory()
{
File outputDir=null;
String externalStorageState=Environment.getExternalStorageState();
if(externalStorageState.equals(Environment.MEDIA_MOUNTED)) {
File pictureDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
outputDir=new File(new File(pictureDir,"Adriagate"),"Online");
if(!outputDir.exists())//V.P. if directory/ies not exist/s it will be created. mksdrs method follows file structure and creates directory by direcory if needed
{
if(!outputDir.mkdirs())
{
return null;
}
}
}
return outputDir;
}
I have put provider inside AndroidManifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"></meta-data>
</provider>
And this is file_paths.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files name="Whatever_Nothing_Works"/>
You are storing your images in Environment.getExternalStoragePublicDirectory(). That is not one of the supported roots for FileProvider. Plus, your XML has <external-files>, and that is not a supported element for FileProvider (valid ones all end in -path).
Options include:
Changing the location of where you are storing the images to one of the locations supported by FileProvider and adjusting your XML to match
Switching to my StreamProvider and using <external-public-path dir="Pictures">
Changing your XML to <external-path name="whatever" path="Pictures"> and hope for the best
Add this in Menifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Replace in file_paths.xml with
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
</paths>
Your file is stored under getExternalFilesDir(). That maps to <external-files-path>, not <files-path>
My goal is to create a XML file on internal storage and then send it through the share Intent.
I'm able to create a XML file using this code
FileOutputStream outputStream = context.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
PrintStream printStream = new PrintStream(outputStream);
String xml = this.writeXml(); // get XML here
printStream.println(xml);
printStream.close();
I'm stuck trying to retrieve a Uri to the output file in order to share it. I first tried to access the file by converting the file to a Uri
File outFile = context.getFileStreamPath(fileName);
return Uri.fromFile(outFile);
This returns file:///data/data/com.my.package/files/myfile.xml but I cannot appear to attach this to an email, upload, etc.
If I manually check the file length, it's proper and shows there is a reasonable file size.
Next I created a content provider and tried to reference the file and it isn't a valid handle to the file. The ContentProvider doesn't ever seem to be called a any point.
Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName);
return uri;
This returns content://com.my.package.provider/myfile.xml but I check the file and it's zero length.
How do I access files properly? Do I need to create the file with the content provider? If so, how?
Update
Here is the code I'm using to share. If I select Gmail, it does show as an attachment but when I send it gives an error Couldn't show attachment and the email that arrives has no attachment.
public void onClick(View view) {
Log.d(TAG, "onClick " + view.getId());
switch (view.getId()) {
case R.id.share_cancel:
setResult(RESULT_CANCELED, getIntent());
finish();
break;
case R.id.share_share:
MyXml xml = new MyXml();
Uri uri;
try {
uri = xml.writeXmlToFile(getApplicationContext(), "myfile.xml");
//uri is "file:///data/data/com.my.package/files/myfile.xml"
Log.d(TAG, "Share URI: " + uri.toString() + " path: " + uri.getPath());
File f = new File(uri.getPath());
Log.d(TAG, "File length: " + f.length());
// shows a valid file size
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("text/plain");
startActivity(Intent.createChooser(shareIntent, "Share"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
break;
}
}
I noticed that there is an Exception thrown here from inside createChooser(...), but I can't figure out why it's thrown.
E/ActivityThread(572): Activity
com.android.internal.app.ChooserActivity has leaked IntentReceiver
com.android.internal.app.ResolverActivity$1#4148d658 that was
originally registered here. Are you missing a call to
unregisterReceiver()?
I've researched this error and can't find anything obvious. Both of these links suggest that I need to unregister a receiver.
ChooserActivity has leaked IntentReceiver
Why does Intent.createChooser() need a BroadcastReceiver and how to implement?
I have a receiver setup, but it's for an AlarmManager that is set elsewhere and doesn't require the app to register / unregister.
Code for openFile(...)
In case it's needed, here is the content provider I've created.
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String fileLocation = getContext().getCacheDir() + "/" + uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
}
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);
}
}
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.
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"));
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
This is what i'm using:
I combined some answers and used the current AndroidX Doku:
Sharing files Android Development
Basic Process: You change the manifest to make it possible for other apps to access your local files. the filepath's that are allowed to be accessed from outside are found in the res/xml/filepaths.xml. When sharing you create an intent to share and set a Flag that temporarily allowed the other app to access your local files. Android documentation claims this is the secure way to share files
Step1: Add FileProvider to Manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.YOUR.APP.PACKAGE.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
Step2: Add filepaths.xml to res/xml (if XML folder does not exists just create it yourself)
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="share/" name="share" />
</paths>
Step3: Use a function like this to start a file share. this function moves the file to the predefined share folder and creates a Url to it. the ShareDir is the File pointing to the files/share/ directory. the copy_File function copies the given file to the share directory in order to be accessible from the outside.
The function also makes it possible to Send the File as email with given header and body. if not needed just set it to empty strings
public void ShareFiles(Activity activity, List<File> files, String header, String body) {
ArrayList<Uri> uriList = new ArrayList<>();
if(files != null) {
for (File f : files) {
if (f.exists()) {
File file_in_share = copy_File(f, ShareDir);
if(file_in_share == null)
continue;
// Use the FileProvider to get a content URI
try {
Uri fileUri = FileProvider.getUriForFile(
activity,
"com.YOUR.APP.PACKAGE.fileprovider",
file_in_share);
uriList.add(fileUri);
} catch (IllegalArgumentException e) {
Log.e("File Selector",
"The selected file can't be shared: " + f.toString());
}
}
}
}
if(uriList.size() == 0)
{
Log.w("StorageModule", "ShareFiles: no files to share");
return;
}
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType("text/html");
intent.putExtra(Intent.EXTRA_SUBJECT, header);
intent.putExtra(Intent.EXTRA_TEXT, body);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
activity.startActivity(Intent.createChooser(intent, "Share Files"));
}