Share Privately Stored Image to External App - Android - android

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.

Related

Android: Send e-mail via intent with file as attachment

There are many posts on this topic, but I can't find the solution for my problem...
Following: I would like to send a file out of my app via an e-mail attachment.
Sending the file via Whatsapp, save to Google Drive,... works, but not for K-9 Mail or Gmail ("Unable to attache file" Toast message is displayed).
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
intentShareFile.setType("application/zip");
intentShareFile.putExtra(Intent.EXTRA_STREAM, Uri.parse("/sdcard/Download/ExportFile.zip"));
//intentShareFile.putExtra(Intent.EXTRA_TEXT, "message");
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intentShareFile.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
I don't understand why it works for all apps, except e-mail apps.
Can anyone help me out?Thanks in advance.
I think your issue is related to the path of the file you are using here (i.e Uri.parse("/sdcard/Download/ExportFile.zip"))
There is a code example to send an email with an attachment on the Android Developers docs here. In the example they pass the Uri as follows:
Uri.parse("content://path/to/email/attachment"). Notice this is called a ContentUri, read more about it form here.
Hope this turns useful for you!
Amr EIZawawy is right. I need to create the Uri of the file and use the File Provider API.
I don't know if I did too much, but this is my solution:
Create a file called "file_paths.xml" in a directory "xml" (create first) within your "res" directory (sibling of layout directory).
The file needs to contain the path to the file you want to share. For the external Download directory this is:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="download" path="Download/"/>
</paths>
// The internal path you'd be <files-path name.../> see [FileProvider][1] for all possibilities
Define the File provider within the AndroidManifest.xml with a meta-data link to the just created XML file:
<application
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.<your-app-package-name>.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" /> // Link to the above created file
</provider>
...
Implement the code:
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
intentShareFile.setType("application/zip");
File fileDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File newFile = new File(fileDirectory, "ExportFile.zip");
String authority = "com.bennoss.myergometertrainer.fileprovider"; // Must be the same as in AndroidManifest.
Uri contentUri = FileProvider.getUriForFile(getContext(), authority, newFile);
intentShareFile.putExtra(Intent.EXTRA_STREAM, contentUri);
//intentShareFile.putExtra(Intent.EXTRA_TEXT, "xxx");
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
For more information see: FileProvider

Unable to open PDF from internal storage for viewing on Android 8 and 9 using File Provider

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);

Keep getting FileNotFound exception after writing to file then opening

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.

File provider - Files not accessible even in the own app

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.

android share image in ImageView without saving in sd card

I want to Share the image in image view.but i don't want save to SDcard.
But when i use Intent to share i used code
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/jpeg");
share.putExtra(Intent.EXTRA_STREAM,Uri.parse(path));
startActivity(Intent.createChooser(share, "Share Image"));
here Path specified location of image in sdcard
but i don't want save image ... is there possible ..
The recommended method for sharing files with other apps is with a ContentProvider called FileProvider. The documentation is pretty good, but some parts are a little tricky. Here is a summary.
Set up the FileProvider in the Manifest
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
...
</application>
</manifest>
Replace com.example.myapp with your app package name.
Create res/xml/filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="shared_images" path="images/"/>
</paths>
This tells the FileProvider where to get the files to share (using the cache directory in this case).
Save the image to internal storage
// save bitmap to cache directory
try {
File cachePath = new File(context.getCacheDir(), "images");
cachePath.mkdirs(); // don't forget to make the directory
FileOutputStream stream = new FileOutputStream(new File(cachePath, "image.png")); // overwrites this image every time
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Share the image
File imagePath = new File(context.getCacheDir(), "images");
File newFile = new File(imagePath, "image.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.app.fileprovider", newFile);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
startActivity(Intent.createChooser(shareIntent, "Choose an app"));
}
Further reading
FileProvider
Storage Options - Internal Storage
Sharing Files
Saving Files
I was able to do this:
File file = new File( getCacheDir(), "screenshot.png");
...
file.setReadable(true, false);
Uri uri = Uri.fromFile(file);
...
intent.putExtra(Intent.EXTRA_STREAM, uri);
This way I save the file in my own cache folder, so I'm not fooling with any public folders or depending on the sd card being present.
Also, this way it gets deleted automatically if the user removes my app, but I also used startActivityForResult/onActivityResult to delete the file and set its folder back to private when the share is finished.
(Personally I was hoping to find a way to share a bitstream and avoid the step of creating a file entirely, but I don't think that is possible.)
[EDIT: I discovered this doesn't work on 2.3.6 where the file MUST be on file:///mnt/sdcard.]
You could also share it as the Media Gallery content provider URI (if the image is already on the phone, or if it came from the web you could share the URL (although it's not the same effect).
But if came from the web and you directly decoded to Bitmap and now want to share it as a proper image, yeah, you really need the file!

Categories

Resources