My Problem: When openFile() intent attempts StartActivityForResult, the application hangs with a blank screen and circular cursor. The file(s) are stored to app.
What I have done: Before and after this issue I researched how to open files by use of Intents. I found a number of similar but different approaches and either used the literal example or a combination of various examples to see if I could find and resolve the issue (see some below). I'm not receiving any type of error message that I have found, and I had the FileUriExposedException previously but resolved it. These files are stored to the app and it may be a permissions issue and have tried what I know, including updating the manifest with for External Read and Write and added a flag setting on the intent for access.
What I'm trying to do: I'm trying to learn how to open files through intents with simple JPG images, then eventually expand to opening various file types after I understand how to do so.
Below is the current code I'm using and it included a MimeTypeMap that I tried in place of "image/jpeg" in case my syntax was not correct on the MIME Type, which did not seem to have an impact.
private void openFile(Uri uri, String fName) {
MimeTypeMap myMime = MimeTypeMap.getSingleton();
String mimeType = myMime.getMimeTypeFromExtension(getUpperBound(fName));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("content://" + uri), "image/jpeg");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, 2);
}
An image of the resulting issue hanging and never opening below:
Some of the referenced links I tried:
https://developer.android.com/guide/components/intents-common
ACTION_VIEW intent for a file with unknown MimeType
Open File With Intent (Android)
http://www.androidsnippets.com/open-any-type-of-file-with-default-intent.html
I was able to resolve my issue combined with S-Sh's second suggestion of considering the use of FileProvider. I had to perform some refinements and ended up with the following to work. I also provided links to the sources in which I used.
Context of the solution:
As a note to future readers as to the context of this solution within my Android app, the code below is launched by clicking a "clickable" TableRow in a TableLayout. The TableLayout lists each filename and file ID as a TableRow. Once a row is clicked, the onClick method of the selected TableRow the filename is acquired and the File class and FileProvider are used and filename passed to create an Uri. This Uri is then passed to an openFile(Uri uri) method I created encapsulating (so-to-speak) the Intent used to open the file.
Code
Adding of the FileProvider to the AndroidManifest.xml' within the` tag:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.mistywillow.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Creating within res directory the file_paths.xml and xml directory (res/xml/):
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="myFiles" path="." />
</paths>
The onClick capturing the filename and preparing the Uri to be passed to the openFile():
row.setOnClickListener(v -> {
TableRow tablerow = (TableRow) v;
TextView sample = (TextView) tablerow.getChildAt(1);
String result = sample.getText().toString();
File filePaths = new File(getFilesDir().toString());
File newFile = new File(filePaths, result);
Uri contentUri = getUriForFile(getApplicationContext(), "com.mydomain.fileprovider", newFile);
openFile(contentUri);
});
The openFile(Uri uri) method containing the Intent to open a file:
private void openFile(Uri uri){
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 2);
}
Links referenced:
https://developer.android.com/reference/androidx/core/content/FileProvider
FileProvider - IllegalArgumentException: Failed to find configured root
Try use origin Uri itself:
intent.setDataAndType(uri, "image/jpeg");
Related
Unfortunately, all existing answers to this topic are totally outdated.
I wish to launch an Intent that will open an external app with a specific images folder.
Since Android 7.0, the following method does not work:
Intent i=new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setDataAndType(Uri.fromFile(new File(path)), "image/*");
startActivity(i);
I am trying using a FileProvider.
MyFileProvider:
public class MyFileProvider extends FileProvider {
}
from AndroidManifest.xml:
<provider
android:name=".util.MyFileProvider"
android:authorities="${applicationId}.common.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/my_file_paths"/>
</provider>
my_file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="files"
path="/My_bugreport"/>
<external-path name="external_files" path="."/>
</paths>
Intent launch code in main activity onCreate():
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath() + "/Pirin 1/"; // Some hard-coded folder I have on my device
Log.d(TAG, "MyDashboardActivity: onCreate: path=" + path);
Uri uri = FileProvider.getUriForFile(this, getPackageName() + ".common.fileprovider", new File(path));
grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.setDataAndType(uri, "image/*");
startActivity(i);
I am getting the Photos app with an empty black screen and a spinning loader.
LogCat doesn't contain any useful info.
Device: Google Pixel 3
Tried many things already, many different paths, etc., almost giving up...
I wish to launch an Intent that will open an external app with a specific images folder.
There is no official support for this in Android.
Unfortunately, all existing answers to this topic are totally outdated.
Any answer other than "there is no reliable way to do this" is wrong.
Since Android 7.0, the following method does not work
It does not work prior to Android 7.0 either. If path points to a directory, then you are lying to the third-party app, saying that there is an image at that location, when there is no image there (since it is a directory, not a file). I would expect most ACTION_VIEW apps to not handle that well. Expecting them to magically show the contents of the directory is just wishful thinking — there is no requirement for an app that supports ACTION_VIEW for an image to have any ability to show the contents of a directory, let alone fall back to that behavior upon receiving a corrupt ACTION_VIEW Intent.
I am trying using a FileProvider
FileProvider does not serve directories in a way that would fit your needs. For example, there is no means with FileProvider to get the list of content at a particular directory.
Moreover, since ACTION_VIEW does not support directories, you would be back where you started.
(also note that your grantUriPermission() call isn't going to do anything meaningful)
For Android 8 and 9 only.
I have a PDF filer here -
String url = "file:///storage/emulated/0/Android/data/com.verna.poc/files/Download/mypdf.pdf";
I'm trying to open this file for viewing using this -
File file= new File(url);
file.setReadable(true, false);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri pdfUri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file);
intent.setDataAndType(pdfUri, "application/pdf");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
List<ResolveInfo> resInfoList = getApplicationContext().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
getApplicationContext().grantUriPermission(packageName, pdfUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
Intent in = Intent.createChooser(intent, "Open File");
startActivity(in);
The File chooser option is opening and when I open the file using Google PDF reader, the PDF reader opens and closes immediately. Whats wrong in my code ?
access the file from within the app which owns that private directory - an Intent won't cut it, because this could have been sent by just any application. and if it has to be a file-chooser Intent, then create a shared directory for your app on the SD card, where any application can access it; the regular Downloads directory would also be suitable for that.
another option (as initially suggested) would be to create a simple file-chooser, which resides within the application, so that no Intent would be required to select a file... this all has certain advances and dis-advances; choose the one possibility, which suits you the best... in general, it's private vs. shared storage location.
You are trying to share a file from internal storage with the another app. You will need to create a file provider for this to work. You will need to specify a directory which you want the file provider to generate Uris for
FileProvider is a special subclass of ContentProvider that facilitates secure sharing of files associated with an app by creating a content:// Uri for a file instead of a file:/// Uri.
A FileProvider can only generate a content URI for files in directories that you specify beforehand. To specify a directory, specify the its storage area and path in XML, using child elements of the element. For example, the following paths element tells FileProvider that you intend to request content URIs for the images/ subdirectory of your private file area.
This answer has a good example on this
link
Here is the doc page for FileProvider
FileProvider
In this we first save the file to internal storage and then read from it using external app.
I use below method to save the file in internal storage :
private void savePDFtoInternalStorage(byte[] pdfAsBytes){
//Save in internal memo cache
File directory = mFragmentActivity.getFilesDir();
//updating path for pdf to match with file_path.xml
mCardStmtFile = new File(directory.getAbsolutePath(), "sample.pdf");
OutputStream outputStream = null;
try {
PbLogger.e(TAG, "writing to mStmtFile");
outputStream = new FileOutputStream(mCardStmtFile, false);
outputStream.write(pdfAsBytes);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
PFB the file provider declared file_path.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path path="/" name="secretpdf" />
</paths>
PFB the Android Manifest Entry for file provider :
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.***.********.provider"
android:exported="false"
android:grantUriPermissions="true">
<!-- ressource file to create -->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
Use below code to launch PDF :
Intent intentShareFile = new Intent(Intent.ACTION_VIEW);
if (mStmtFile.exists()) {
intentShareFile.setType("application/pdf");
Uri fileUri = FileProvider.getUriForFile(
mFragmentActivity,
"com.****.********.provider",
mCardStmtFile);
intentShareFile.putExtra(Intent.EXTRA_STREAM, fileUri);
intentShareFile.putExtra(Intent.EXTRA_SUBJECT,
getDescription());
//adding grant read permission to share internal file
intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
}
I got the issue. When you are making a new file using, File file = new File(path), don't add file:// infront of the path.
This is correct -
String url = "/storage/emulated/0/Android/data/com.verna.poc/files/Download/mypdf.pdf";
File file= new File(url);
This is wrong -
String url = "file:///storage/emulated/0/Android/data/com.verna.poc/files/Download/mypdf.pdf";
File file= new File(url);
I am trying to share an image via classic Intent. I have added the following items:
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="my_images" path="." />
</paths>
Manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
And finally MainActivity.java:
private void shareFile(String fileName) {
Intent share = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(this.getApplicationInfo().dataDir + "/app_flutter/userphotos", fileName));
share.setData(uri);
share.setType("image/png");
share.putExtra(Intent.EXTRA_STREAM, uri);
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(share, "Share"));
}
The problem I am facing is that image I am trying to share has the following path:
/data/user/0/shoedrobe.innovativeproposals.com.shoedrobe/app_flutter/userphotos/receipt20181101094430.jpg
However the FileProvider is trying to access it from here:
java.lang.IllegalArgumentException: Failed to find configured root
that contains
/data/data/shoedrobe.innovativeproposals.com.shoedrobe/app_flutter/userphotos/receipt20181101094430.jpg
For saving images I am using package path_provider and I am saving items under
getApplicationDocumentsDirectory(), which in Android is AppData directory.
I am not sure why FileProvider decided suddenly to go from /data/user/0/ to /data/data/ folder, therefore any help or tips regarding this matter would be highly appreciated.
Update:
I have updated the code as per recommendations and replaced the Uri under MainActivity.java with the following line:
Uri uri = FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(this.getDir("flutter", Context.MODE_PRIVATE).getPath() + "/app_flutter/userphotos", path));
Nonetheless, the problem still persists (same exception that file is supposed to be under /data/data/<package> instead of /data/user/0. I have also tried to add additional persmissions to my AndroidManifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
but it did not work as well. Could the problem lie with my file_paths.xml file?
In the end, none of the proposed solutions did not work; neither using extenal-path, nor the other solution.
The way how I managed in the end to share file was to actually copy it to a temporary (cache) directory first (in flutter path_provider it's getTemporaryDirectory()) and update file_paths.xml to following:
<cache-path name="image" path="."/>
and finally under MainActivity.java:
Uri uri = FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(this.getCacheDir(), path));
Under the hood, Flutter's getApplicationDocumentsDirectory() is using getDir() instead of dataDir, which could be different.
From the Android documentation on getDataDir(), which is equivalent to this.getApplicationInfo().dataDir:
Returns the absolute path to the directory on the filesystem where all private files belonging to this app are stored. Apps should not use this path directly; they should instead use getFilesDir(), getCacheDir(), getDir(String, int), or other storage APIs on this class.
The returned path may change over time if the calling app is moved to an adopted storage device, so only relative paths should be persisted.
Therefore, to ensure consistency between the directory used for saving images by Flutter and the directory used to retrieve the files via FileProvider, the getUriForFile line in MainActivity.java could be modified like this:
FileProvider.getUriForFile(this, "com.example.android.fileprovider", new File(<your application context>.getDir("flutter", Context.MODE_PRIVATE).getPath() + "/app_flutter/userphotos", fileName));
... replacing <your application context> with the variable storing your application's context, if any.
I am implementing a File Provider, followed the doc carefully but unable to use the files in the app itself or share any file.
Added manifest, created xml file for dir sharing, generating content uri, granting uri permission, tried sharing files with cache, external, internal dir but nothing is working.
Searched the net but found nothing which is missing in the code.
Below is the code:
Manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="package.widget.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
filepaths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="widgetDoc" path="."/>
</paths>
GetUri:
private Uri getFileUri(Context context, String name){
File newFile = new File(context.getCacheDir() + File.separator + StorageUtil.INTERNAL_DIR, name);
Uri contentUri = FileProvider.getUriForFile(context, "package.widget.fileprovider", newFile);
return contentUri;
}
Code to access pdf file:
Intent target = new Intent(Intent.ACTION_VIEW);
Uri uri = getFileUri(getApplicationContext(), file);
target.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
target.setDataAndType(uri,"application/pdf");
Intent intent = Intent.createChooser(target, "Choose Pdf Viewer...");
startActivity(intent);
Code to access image:
imageview.setImageURI(getFileUri(getApplicationContext(), file));
Kindly help me out where I am going wrong, not even able to use these files in my own app too.
Thanks in advance.
I need to show these image files in a widget and now if am accessing the image files from the widget then it is giving IllegalArgumentException: Failed to find configured root however it is working fine with activity
Ideally, use setImageViewBitmap(), and make sure that your image is small (under 1MB in heap space, such as less than 512x512).
While the RemoteViews can accept a Uri, you have good way of associating Intent.FLAG_GRANT_READ_URI_PERMISSION with a RemoteViews for the purposes of populating an ImageView. And, you cannot export your FileProvider, as FileProvider does not allow that.
You can attempt to identify the user's chosen home screen and use grantUriPermission() to grant the home screen access to this Uri, but I would expect that solution to be fragile.
This question contained several sub-questions. I am forking these, starting by this question. I'll eventually clean up by deleting this question.
The following program will in theory share a hello-world text file. The code runs, but sharing to either Dropbox or to Gmail (by way of just two concrete examples) fails.
public class MainActivity extends Activity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String filename = "hellow.txt";
String fileContents = "Hello, World!\n";
byte[] bytes = fileContents.getBytes();
FileOutputStream fos = null;
try {
fos = this.openFileOutput(filename, MODE_PRIVATE);
fos.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
File file = new File(filename);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
shareIntent.setType("application/txt");
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));
file.delete();
}
}
Aside from adding a value for send_to in res/values/strings.xml, the only other pair of changes I did to the generic Hello, World that Eclipse creates is adding the following <provider> tag in AndroidManifest.xml:
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mycorp.helloworldtxtfileprovider.MainActivity"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/my_paths" />
</provider>
<activity
android:name="com.mycorp.helloworldtxtfileprovider.MainActivity"
...
... and adding the following in res/xml/my_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="." />
</paths>
My main question is the first, but while you're at this topic, a discussion of questions 2-4 would also be interesting.
Why does the program above fail?
Is it indeed the case that if one needs a custom ContentProvider, then one needs to extend that class, but if one just needs a FileProvider, then one can use that class without derivation?
In this code, I needed to use filename twice—once with openFileOutput and another with new File(). Is there a way to avoid this duplication (that would guarantee that the same file is being referenced)?
Is it safe to delete the file right after startActivity(..) is called, or is it necessary to devise a callback to wait learning that the file has been uploaded/shared. (The real file may take some time to share/upload.)
Edit
The code runs fine and shows a list of apps to send to.
If I select Dropbox, I can select the location just fine. Dropbox sends the notifications "Uploading to Dropbox" followed by "Upload failed: my_file.txt".
If I select Gmail, I can fill the recipient and the file appears to be attached, but after "sending message.." I get "Couldn't send attachment".
1.
Use FileProvider.getUriForFile(...) to construct the URI. This will direct the started activity to your FileProvider (which can then serve the file from your app's private files directory). Uri.fromFile(...) does not work because the started activity will try to directly access the private directory.
Set FLAG_GRANT_READ_URI_PERMISSION so that the started activity is granted read permission for the URI constructed by the FileProvider.
Finally, "text/plain" might work better than "application/txt" as MIME type.
I've had some problems getting this to work consistently across devices. This is my best bet so far (will edit if I ever refine it):
final String auth = "org.volley.sndcard_android.fileprovider";
final Uri uri = FileProvider.getUriForFile(activity, auth, file);
final String type = activity.getContentResolver().getType(uriForFile);
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setDataAndType(uri, type);
shareIntent.putExtra(Intent.EXTRA_STREAM, uriForFile);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent chooser = Intent.createChooser(shareIntent, "<title>");
activity.startActivity(chooser);
Setting only the type works on my Nexus 5 but not on my Samsung tablet. It seems the Samsung tablet needs the URI as data in order to grant the permission. Also note that intent.getData() cancels any previous calls to intent.setType() and vice versa, so you have to use the combined method, as done above.
Gmail seems to interpret the additional data as a default To-address. Highly annoying! If anyone has a better solution, please share it (pun intended, I'm from Gothenburg).
2.Yes, it is indeed. You see that ContentProvider is an abstract class, so to use custom content provider one must have to extend it. As FileProvider is a subclass of ContentProvider (which is not abstract), programmers can use FileProvider without subclassing it.
3.For ensuring same file you can follow the sample code below -
String fileName = "my_file.txt";
String fileData = "sample file content";
// Get the file
File file = new File(this.getFilesDir(), fileName);
if (!file.exists()) {
file.createNewFile();
}
// Write data to file
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(fileData);
fileWriter.close();
// Get the uri
Uri uri = Uri.fromFile(file);
// Share the file with corresponding uri
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("application/txt");
startActivity(Intent.createChooser(shareIntent, "Send To"));
4.No, it's not safe to delete the file right after you call startActivity(). Because startActivity() is non-blocking function call and it'll return immediately. You have to wait for the time the file is being shared. You can do that by using startActivityForResult() . See if it serves the purpose.