I have been trying this for quite a few hours now so I'm hoping someone could point out what I'm doing wrong. Basically I am pulling a vCard string from the database and passing it off to a method which should open the vCard on the user's phone. I decided on using the FileProvider class because I need to share the file with the user's Contacts application so that the vCard can be stored in their phone and because I want to delete the file after this is done.
Here is my implementation:
private void downloadVCard(final String vCardString, Context context) {
try {
File vcardPath = new File(context.getFilesDir(), "vcards");
File vcardFile = new File(vcardPath, "vcard.vcf");
FileOutputStream outputStream = new FileOutputStream(vcardFile);
outputStream.write(vCardString.getBytes());
outputStream.close();
Uri contentUri = getUriForFile(context, "com.goartech.goar.fileprovider", vcardFile);
// Let another application open the vcard
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(contentUri, "text/x-vcard");
startActivity(intent);
} catch (IOException e) {
e.printStackTrace();
}
}
My FileProvider setup looks like:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.goartech.goar.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
My filepaths.xml looks like:
<paths>
<files-path path="vcards/" name="vcards" />
</paths>
I had it working at one point where the Contacts app was trying to open the vCard file (there was no chooser presented though) but then the app would crash after a few seconds. I thought maybe it was the vCard structure so I made a very simple one and tested it, still nothing. Then I thought maybe because it was trying to open the wrong file and restructured things, but now I'm getting the FileNotFound exception again.
Thanks in advance.
Related
My App is creating a PDF and passes it to other Apps to be displayed elsewhere. I create the file in internal storage to have to ask the user for less permissions.
I create my intent via:
Intent viewIntent = new Intent(Intent.ActionView);
Java.IO.File document = new Java.IO.File(filePath);
Android.Net.Uri contentUri = FileProvider.GetUriForFile(
_context,
_context.PackageName + ".provider",
document);
viewIntent.SetDataAndType(contentUri, GetMimeType(document));
viewIntent.SetFlags(ActivityFlags.NewTask);
viewIntent.AddFlags(ActivityFlags.GrantReadUriPermission);
viewIntent.AddFlags(ActivityFlags.ClearTask);
viewIntent.AddFlags(ActivityFlags.GrantPersistableUriPermission);
viewIntent.AddFlags(ActivityFlags.GrantPrefixUriPermission);
viewIntent.AddFlags(ActivityFlags.GrantWriteUriPermission);
Intent chooser = Intent.CreateChooser(viewIntent, "");
chooser.SetFlags(ActivityFlags.NewTask);
chooser.AddFlags(ActivityFlags.GrantReadUriPermission);
chooser.AddFlags(ActivityFlags.ClearTask);
chooser.AddFlags(ActivityFlags.GrantPersistableUriPermission);
chooser.AddFlags(ActivityFlags.GrantPrefixUriPermission);
chooser.AddFlags(ActivityFlags.GrantWriteUriPermission);
_context.StartActivity(viewIntent);
On the Google Pixel 3 XL where I test, I can open a PDF without any issues.
When I do the same on a Huawei tablet with API level 24, sometimes everything works but at other times Adobe Acrobat shows an error: This file could not be accessed. Check the location or the network and try again.
The behavior isn't deterministic, sometimes I get the error but other times everything works fine.
In the Application node of your Android Manifest make sure you've added a FileProvider definition:
<provider android:name="android.support.v4.content.FileProvider" 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>
Add a Resources/xml/provider_paths file with the contents:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
You can restrict this later if needed. However, for my use case I put files in external storage, which I share from. You need to adjust this accordingly where you are sharing from.
I.e. if you are sharing the file from internal app storage you will need to add a files-path definition there too.
Then when sharing a file you simply do:
var packageName = context.ApplicationInfo.PackageName;
var fileProviderName = $"{packageName}.FileProvider";
var intent = new Intent(Intent.ActionSend);
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
intent.SetType("image/*"); // change mime type if wanting to open in other app
intent.PutExtra(Intent.ExtraStream,
FileProvider.GetUriForFile(context, fileProviderName, new Java.IO.File(filePath)));
StartActivity(intent);
That should be enough, works fine every time for me for sharing images to another app. I don't think you need the flags for your chooser Intent, only for the inner viewIntent. Also the GrantReadUriPermission should be the only thing needed if you are providing flags.
I know that there are a lot of questions at SO about this problem. I tried about 10 different approaches and i couldn't make it work right. I always get next error:
java.lang.IllegalArgumentException: Failed to find configured root that contains /file:/storage/emulated/0/Android/data/app.kwork/files/IMG-afbfdc57c016fb1ff7dd983a056edffa-V.jpg
Official tutorial also didn't help. I'll share my code below, maybe i do something wrong, and you know what exactly is wrong here..
Manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="app.kwork.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
provider_paths.xml
<paths>
<files-path path="." name="files" />
</paths>
I save files into:
File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator + fileName);
Example of the file path: file:///storage/emulated/0/Android/data/app.kwork/files/IMG-afbfdc57c016fb1ff7dd983a056edffa-V.jpg
And try to share this file with next code:
public void openFile(String filePath){
File file = new File(filePath);
Log.d(TAG, "openFile: filePath: " + filePath);
Uri photoURI = FileProvider.getUriForFile(context, "app.kwork.provider", file);
MimeTypeMap myMime = MimeTypeMap.getSingleton();
Intent newIntent = new Intent(Intent.ACTION_VIEW);
String mimeType = myMime.getMimeTypeFromExtension(Utils.fileExt(filePath));
newIntent.setDataAndType(photoURI,mimeType);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(newIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, "No handler for this type of file.", Toast.LENGTH_LONG).show();
}
}
I tried different authorities and paths in xml, but i'm here now.
UPD: Testing on Android Oreo
Per the FileProvider documentation, it is <external-files-path> that corresponds with getExternalFilesDir() - the <files-path> you are using corresponds with getFilesDir().
You must change your paths.xml to use <external-files-path> or store your files in getFilesDir().
Example of the file path: file:///storage/emulated/0/Android/data/app.kwork/files/IMG-afbfdc57c016fb1ff7dd983a056edffa-V.jpg
This is not a file path. This is a Uri.
The file path is /storage/emulated/0/Android/data/app.kwork/files/IMG-afbfdc57c016fb1ff7dd983a056edffa-V.jpg.
Do not pass a string form of a Uri to the File constructor. You will wind up with a malformed File, and getUriForFile() will not be able to deal with that malformed File.
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.
I'm trying to save an image to my app's private filesystem then share that image with external apps. I've looked into it, and it seems like the best way to do it is just to save onto the filesDir because if you use the externalFiles directory, some devices may not have one. Here's my code so far (simplified for brevity):
Activity with my image
public Uri saveImageAndGetUri(Context context) {
String fileName = "test.png";
File imageDirectory = new File(getFilesDir(), "images");
File savedImage = new File(imageDirectory, fileName);
FileOutputStream stream;
try {
stream = new FileOutputStream(savedImage);
getCurrentImage().compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.flush();
stream.close();
} catch(Exception e) {}
return FileProvider.getUriForFile(context, "com.mydomain.mypackage.Dialogs.ShareOptions", savedImage);
}
ShareOptions.java
Uri contentUri = activity.saveImageAndGetUri(getContext()); //This calls the method above
intent = new Intent(Intent.ACTION_SEND);
getContext().grantUriPermission("com.twitter.android", contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); //Grant read permission to Twitter. Don't think this part is working
getContext().grantUriPermission("com.twitter.android", contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); //Grant write permission to Twitter. Don't think this part is working
intent.setPackage("com.twitter.android");
intent.setType("image/png");
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
AndroidManifest.xml
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.mypackage.Dialogs.ShareOptions"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
xml/file_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
</paths>
As soon as the intent is sent, the Twitter app crashes. I've looked into the error log in Twitter and it seems like it may be an issue with permissions. I think somehow Twitter doesn't have permission to access the Uri even with the code I currently have.
Any and all help is appreciated!
The reason it doesn't work is many 3rd-party apps don't support FileProvider right now. See this question: Image share intent works for Gmail but crashes FB and twitter.
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.