I have image stored in the internal storage of the app. I can grab the path and I can succesfully set it to image view. But when I am trying to let the user open it using the gallery (intent), it displays black screen.
myIntent.setDataAndType(Uri.fromFile(file), mimetype);
intent = Intent.createChooser(myIntent, "Choose a viewer");
startActivity(intent);
I am pretty sure it has to do with permission that gallery cant access private storage of my app for some reason. But is there way to do that "beside moving the file to external storage"
Thanks
Use FileProvider to serve the file from internal storage. Quoting the documentation:
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 content URI allows you to grant read and write access using temporary access permissions. When you create an Intent containing a content URI, in order to send the content URI to a client app, you can also call Intent.setFlags() to add permissions. These permissions are available to the client app for as long as the stack for a receiving Activity is active.
Here is an easy 'single file based solution'
When ever you add a file, let Media Store Content Provider knows about it using
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.fromFile(imageAdded)));
i have done some thing like this to show images from gallery.
private void pickFromGallery() {
Crop.pickImage(this);
}
<--->
crop is an android class.
this is a function in crop class.
public static void pickImage(Activity activity) {
Intent intent = (new Intent("android.intent.action.GET_CONTENT")).setType("image/*");
try {
activity.startActivityForResult(intent, 9162);
} catch (ActivityNotFoundException var3) {
Toast.makeText(activity, string.crop__pick_error, 0).show();
}
}
Related
So I have an android app which opens and displays PDF's, I have the user select pdfs like this
fun openFile() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/pdf"))
putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Files.getContentUri("external"))
}
activity?.startActivityForResult(intent, REQUEST_CODE_PDF_FILE)
}
And then I retrieve the URI from the activity result display it and save the URI. However the next time the app is opened I want to be open that same file, right now when I try opening the saved URI I get the following:
ava.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{c3dfcb2 32587:ca.thing.testapp/u0a237} (pid=32587, uid=10237) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
at android.os.Parcel.createExceptionOrNull(Parcel.java:2425)
at android.os.Parcel.createException(Parcel.java:2409)
at android.os.Parcel.readException(Parcel.java:2392)
at android.os.Parcel.readException(Parcel.java:2334)
at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:5850)
at android.app.ActivityThread.acquireProvider(ActivityThread.java:6973)
So clearly after closing and reopening the app I no longer have permission to use that selected file. So I imagine what I need to do is make a copy of that file into some cache dir that I do have permissions in so that I can display it when the app is reopened. How would I go about doing that?
You should take persistable uri permission in onActivityResult in order to use the uri later.
Making a copy is not needed.
After about a week of pulling my hair out, I'm finally done and ready to ask for some help.
Basically in my app I use the Intent below to create a new PDF, which is done via Storage Access Framework.
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "application/pdf"
intent.putExtra(Intent.EXTRA_TITLE, title)
startActivityForResult(intent, 1234)
After that I get the Uri on the onActivityResult() method, like so:
uri = dataIntent.data
if (uri != null) {
val takeFlags = data.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, takeFlags)
generatePdf(uri)
}
PDF generation is ok, the problem comes when I need to call ACTION_VIEW for the user to see the generated file or to share the file using ACTION_SEND.
Example of ACTION_VIEW usage (Yes, I'm using both Kotlin and Java):
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri, mimeType);
startActivity(intent);
I can't for the life of me figure out how to get an Uri that another app can use.
What I tried so far:
This answer, but the following exception is thrown: java.lang.IllegalArgumentException: column '_data' does not exist. Available columns: [_display_name, _size]
DocumentFile, using DocumentFile.fromFile(file), which turns the Uri from content://com.myapp.provider/root/document/primary:folder-created-by-the-user/generated-pdf.pdf to file:///root/document/primary:folder-created-by-the-user/generated-pdf.pdf, and still no app can open it
Many many other things that I can't even remember anymore
If someone could shed some light on this issue would be truly appreciated.
In principle use the same uri as obtained at creating the file. But ...you cannot grant a read uri permission on that uri. You got it. But you cannot forward such a permission to a viewer of your document.
Instead you should implement a ContentProvider. Then you can serve the content of your file.
Like blackapps said in his response, what I had to do was implement a ContentProvider, more specifically a DocumentProvider.
Following this link and this link is what finally did the trick. I implemented a CustomDocumentProvider that exposes a folder inside my app's private files (context.getFilesDir().getAbsolutePath() + "/folderToExpose"), after that all files created in this folder were exposed to other apps and I could use ACTION_VIEW and ACTION_SEND normally.
If someone happens to come across this issue, just make sure that the folder you want to expose doesn't contain any files that are crucial to your app, like database files, since users will have full access to all of its contents. And if it is a new folder, make sure to create it by calling mkdirs().
I use the Intent mechanism to have the user select an image via the standard way
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
ctx.startActivityForResult(intent, RequestCodes.SelectPhoto)
then I pass the Uri to another activity to maybe crop the photo. I need the Uri before to do some pre-checks.
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity. However, there is a "weird" provider in Asia, com.miui.gallery.provider.GalleryOpenProvider that doesn't -- an evil SecurityException happens in the cropper.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot, but unfortunately that one doesn't support Google Photos in the cloud, in the emulator.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT, and if so either prevent it, or otherwise fall back to using ACTION_OPEN_DOCUMENT. I'd like to avoid copying the stream before giving the Uri to the cropper, the crop activity treats it as readonly anyway.
This the full function to start the crop (kotlin). CropActivity is a modification of the old open-source Gallery app com.android.gallery3d.
private fun startCrop(ctx: Activity, uri: Uri) {
val intent = Intent(ctx, CropActivity::class.java)
intent.data = uri
val file = this.createImageFile(ctx, "photofinal")
if (file == null) {
this.showStorageUnavailable(ctx)
return
}
val outputUri = Uri.fromFile(file)
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
intent.putExtra(CropExtras.KEY_MIN_CROP_SIDE, Config.minimumImageDimension)
intent.putExtra(CropExtras.KEY_MOST_OBLONG_ASPECT, Config.maxPhotoAspectRatio)
intent.putExtra(CropExtras.KEY_EXIF_ORIENTATION, exifOrientation)
ctx.startActivityForResult(intent, RequestCodes.CropPhoto)
}
then I pass the Uri to another activity to maybe crop the photo
Pass that Uri in the "data" facet of the Intent, and add FLAG_GRANT_READ_URI_PERMISSION to transfer read access to the other component. See this sample app:
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
if (resultCode==Activity.RESULT_OK) {
getActivity()
.startService(new Intent(getActivity(), DurablizerService.class)
.setData(resultData.getData())
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
}
Here, I happen to be passing the Uri to a service, but the same principle holds for an activity.
See also this blog post for more about Uri access lifetimes.
Or, don't use separate activities, but do something else (e.g., multiple fragments).
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity.
That would occur if the Uri has a file scheme or is from an exported permission-less ContentProvider.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot
It is subject to the same general rules as the Uri values you get from ACTION_GET_CONTENT.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT
That's not strictly possible. Any app could return a Uri from that provider. In practice, that provider may only be used by its hosting app. If you found the package name for that provider's app, and you used queryIntentActivities() on PackageManager with your ACTION_GET_CONTENT Intent, you could determine if an activity from that app is in the list of ACTION_GET_CONTENT implementations.
However, if you use FLAG_GRANT_READ_URI_PERMISSION, as I note earlier, that should not be necessary.
if so either prevent it
Other than by rolling your own "chooser"-style UI, that's not strictly possible.
My app creates mails with attachments, and uses an intent with Intent.ACTION_SEND to launch a mail app.
It works with all the mail apps I tested with, except for the new Gmail 5.0 (it works with Gmail 4.9), where the mail opens without attachment, showing the error: "Permission denied for the attachment".
There are no useful messages from Gmail on logcat. I only tested Gmail 5.0 on Android KitKat, but on multiple devices.
I create the file for the attachment like this:
String fileName = "file-name_something_like_this";
FileOutputStream output = context.openFileOutput(
fileName, Context.MODE_WORLD_READABLE);
// Write data to output...
output.close();
File fileToSend = new File(context.getFilesDir(), fileName);
I'm aware of the security concerns with MODE_WORLD_READABLE.
I send the intent like this:
public static void compose(
Context context,
String address,
String subject,
String body,
File attachment) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("message/rfc822");
emailIntent.putExtra(
Intent.EXTRA_EMAIL, new String[] { address });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.fromFile(attachment));
Intent chooser = Intent.createChooser(
emailIntent,
context.getString(R.string.send_mail_chooser));
context.startActivity(chooser);
}
Is there anything I do wrong when creating the file or sending the intent? Is there a better way to start a mail app with attachment? Alternatively - has someone encountered this problem and found a workaround for it?
Thanks!
I was able to pass a screenshot .jpeg file from my app to GMail 5.0 through an Intent. The key was in this answer.
Everything I have from #natasky 's code is nearly identical but instead, I have the file's directory as
context.getExternalCacheDir();
Which "represents the external storage directory where you should save cache files" (documentation)
GMail 5.0 added some security checks to attachments it receives from an Intent. These are unrelated to unix permissions, so the fact that the file is readable doesn't matter.
When the attachment Uri is a file://, it'll only accept files from external storage, the private directory of gmail itself, or world-readable files from the private data directory of the calling app.
The problem with this security check is that it relies on gmail being able to find the caller app, which is only reliable when the caller has asked for result. In your code above, you do not ask for result and therefore gmail does not know who the caller is, and rejects your file.
Since it worked for you in 4.9 but not in 5.0, you know it's not a unix permission problem, so the reason must be the new checks.
TL;DR answer:
replace startActivity with startActivityForResult.
Or better yet, use a content provider.
Use getExternalCacheDir() with File.createTempFile.
Use the following to create a temporary file in the external cache directory:
File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());
Then copy your original file's content to tempFile,
FileWriter fw = new FileWriter(tempFile);
FileReader fr = new FileReader(Data.ERR_BAK_FILE);
int c = fr.read();
while (c != -1) {
fw.write(c);
c = fr.read();
}
fr.close();
fw.flush();
fw.close();
now put your file to intent,
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
You should implement a FileProvider, which can create Uris for your app's internal files. Other apps are granted permission to read these Uris. Then, simply instead of calling Uri.fromFile(attachment), you instantiate your FileProvider and use:
fileProvider.getUriForFile(attachment);
Google have an answer for that issue:
Store the data in your own ContentProvider, making sure that other apps have the correct permission to access your provider. The preferred mechanism for providing access is to use per-URI permissions which are temporary and only grant access to the receiving application. An easy way to create a ContentProvider like this is to use the FileProvider helper class.
Use the system MediaStore. The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.
Also you can try set permissions for your file:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
And finally you can copy/store your files in external storage - permissions not needed there.
I tested it and I found out that it was definitely private storage access problem.
When you attach some file to Gmail (over 5.0) do not use the file from private storage such as /data/data/package/. Try to use /storage/sdcard.
You can successfully attach your file.
Not sure why GMail 5.0 doesn't like certain file paths (which I've confirmed it does have read access to), but an apparently better solution is to implement your own ContentProvider class to serve the file. It's actually somewhat simple, and I found a decent example here: http://stephendnicholas.com/archives/974
Be sure to add the tag to your app manifest, and include a "android:grantUriPermissions="true"" within that. You'll also want to implement getType() and return the appropriate MIME type for the file URI, otherwise some apps wont work with this... There's an example of that in the comment section on the link.
I was having this problem and finally found an easy way to send email with attachment. Here is the code
public void SendEmail(){
try {
//saving image
String randomNameOfPic = Calendar.DAY_OF_YEAR+DateFormat.getTimeInstance().toString();
File file = new File(ActivityRecharge.this.getCacheDir(), "slip"+ randomNameOfPic+ ".jpg");
FileOutputStream fOut = new FileOutputStream(file);
myPic.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
file.setReadable(true, false);
//sending email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"zohabali5#gmail.com"});
intent.putExtra(Intent.EXTRA_SUBJECT, "Recharge Account");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
//Uri uri = Uri.parse("file://" + fileAbsolutePath);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email..."),12);
}catch (Exception e){
Toast.makeText(ActivityRecharge.this,"Unable to open Email intent",Toast.LENGTH_LONG).show();
}
}
In this code "myPic" is bitmap which was returned by camera intent
Step 1: Add authority in your attached URI
Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);
Same as your manifest file provide name
android:authorities="com.yourpackage"
Step 2`; Add flag for allow to read
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
I saw a way of reading online pdf files using google docs ...
Android - Load PDF / PDF Viewer
Is there a way we can use it to view local files stored in sd card
You can launch an intent that will allow the user to choose what app will open the PDF with the following code, which will work for any file and mimetype. If the user doesn't have an app that can open it, you can display an error or do whatever else you need to do.
Note that the file must be world-readable, so it must be marked as such if it is on Internal storage, or it must be in external storage.
private void openFile(File f, String mimeType)
{
Intent viewIntent = new Intent();
viewIntent.setAction(Intent.ACTION_VIEW);
viewIntent.setDataAndType(Uri.fromFile(file), mimeType);
// using the packagemanager to query is faster than trying startActivity
// and catching the activity not found exception, which causes a stack unwind.
List<ResolveInfo> resolved = getPackageManager().queryIntentActivities(viewIntent, 0);
if(resolved != null && resolved.size() > 0)
{
startActivity(viewIntent);
}
else
{
// notify the user they can't open it.
}
}