Android - take picture - pass uri to camera app - android

I am trying to follow this tutorial to invoke the camera app and ask it to save a picture at the uri passed in the intent.
File image = File.createTempFile("testImage", ".jpg",
getExternalFilesDir(Environment.DIRECTORY_PICTURES));
Uri uri = Uri.fromFile(image);
Intent i = new Intent();
i.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, uri);
if (i.resolveActivity(getPackageManager()) != null) {
startActivityForResult(i, 1);
}
The path of the image is :
/storage/emulated/0/Android/data/com.android.test1.app/files/Pictures/testImage-516714791.jpg
I want to ask that how did the camera app have permission to write to this path ? I am testing on Android 4.4, so the path ExternalFilesDir is not publicly writable.

The reference says:
There is no security enforced with these files. For example, any application holding WRITE_EXTERNAL_STORAGE can write to these files.
Starting in KITKAT, no permissions are required to read or write to the returned path; it's always accessible to the calling app. This only applies to paths generated for package name of the calling application. To access paths belonging to other packages, WRITE_EXTERNAL_STORAGE and/or READ_EXTERNAL_STORAGE are required.
So, system Camera app, or any other app which is granted WRITE_EXTERNAL_STORAGE permission can write to /storage/emulated/0/Android/data/com.android.test1.app/files/Pictures/.

Related

Copy opened file into app directory for later use

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.

A directory created in shared storage with ACTION_CREATE_DOCUMENT can't be used unless the user consent is asked again with ACTION_OPEN_DOCUMENT_TREE

I am trying to upgrade an app to be copliant with the Android11 shared storage concept. This app needs to store its created gpx files in a storage area not destroyed in case the app is uninstalled. This area shall stay in shared storage and the directory can be created with ACTION_CREATE_DOCUMENT:
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setType(DocumentsContract.Document.MIME_TYPE_DIR);
intent.putExtra(Intent.EXTRA_TITLE, "AGPS-Tracker");
startActivityForResult(intent, SS_CREATE_GPX_DIR_REQUEST);
and in onActivityResult:
Uri uri = data.getData();
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
startDirUri = DocumentsContract.buildTreeDocumentUri(uri.getAuthority(),DocumentsContract.getDocumentId(us));
Then I try to use startDirUri :
uriDir = DocumentsContract.buildDocumentUriUsingTree(startDirUri, DocumentsContract.getTreeDocumentId(startDirUri));
I get: java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/primary%3AAGPS-Tracker%20(11)/document/primary%3AAGPS-Tracker%20(11)/children from pid=7209, uid=10341 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
Then if I use ACTION_OPEN_DOCUMENT_TREE on this new created directory, finally I get the access.
My question is: why a directory created by the user can't be immediately used? Really we need to ask twice the user: first to create and then to use the same directory?

In Nougat Capture Image from Camera and get file from Uri

In Android Nougat and above versions the method to capture image from camera intent is changed and following code is working fine for me.
Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// File file = new File(AppGlobal.URI_CAPTURED_IMAGE.getPath());
AppGlobal.URI_CAPTURED_IMAGE = FileProvider.getUriForFile(parent, context.getPackageName() + ".provider", AppGlobal.getOutputMediaFile());
}
else
{
AppGlobal.URI_CAPTURED_IMAGE = Uri.fromFile(AppGlobal.getOutputMediaFile());
}
camera.putExtra(MediaStore.EXTRA_OUTPUT, AppGlobal.URI_CAPTURED_IMAGE);
camera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(camera, WritePostFragment.REQUEST_CODE_PHOTO_CAPTURE);
In onActivityResult method I'm able to show the captured image in ImageViews as well.
Glide.with(parent).load(AppGlobal.URI_CAPTURED_IMAGE).centerCrop().into(profilePic_iv);
But when i use the same uri to get File then it says no file exists at given path. What could be the issue? How can i parse the Uri to get File. It seems to be version related stuff.
Sample uri is as follows:
content://com.example.demo.provider/external_files/Camera/IMG_20171213_015646.jpg
How can i parse the Uri to get File
You don't. Moreover, you do not need to. Your file is whatever AppGlobal.getOutputMediaFile() returns. You need to hold onto that value (including putting it in the saved instance state Bundle, in case your process is terminated while the camera app is in the foreground). See this sample app for how to use ACTION_IMAGE_CAPTURE with FileProvider.

Attach a pdf to email, grant permission

If I grant READ_EXTERNAL_STORAGE permission all works good, but can I get it to work without asking? It worked without It on my emulator, but on a real device it doesn't work without it.
Output stream
String fileName = "Körjorunal_" + lastUpdateTime + ".PDF";
String path = Environment.getExternalStorageDirectory().toString();
File file = new File(path, fileName)
pdfUri = Uri.fromFile(file);
OutputStream outputStream = new FileOutputStream(file);
document.writeTo(outputStream);
document.close();
outputStream.close();
Intent
// Gets invoked on setup,
mShareIntent = new Intent();
mShareIntent.setAction(Intent.ACTION_SEND);
mShareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
mShareIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Creates document type.
mShareIntent.setType("application/pdf");
// Going via email:
mShareIntent.putExtra(Intent.EXTRA_SUBJECT, "Körjournal i bifogad PDF");
// Attach the PDf Uri.
mShareIntent.putExtra(Intent.EXTRA_STREAM, pdfUri);
mShareActionProvider.setShareIntent(mShareIntent);
Manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
FLAG_GRANT_*_URI_PERMISSION (for READ and WRITE) really are for where the Uri is from your own ContentProvider. If you want to avoid the need for the WRITE_EXTERNAL_STORAGE (and superfluous READ_EXTERNAL_STORAGE) permission:
Step #1: Write the file to internal storage, such as getCacheDir()
Step #2: Serve that file from internal storage using FileProvider
Step #3: Use your existing ACTION_SEND logic, substituting the Uri that you get from FileProvider
You will see that pattern used in this sample project. My sample is for ACTION_VIEW, not ACTION_SEND, but otherwise it is a close match.
One key difference, though, will be in how you grant the permissions, if your minSdkVersion is below 21. Uri values in extras were ignored by FLAG_GRANT_*_URI_PERMISSION on Android 4.4 and below. For those versions, the only solution that I know of is to manually grant rights to every possible app (in your case, every PDF viewer).
In this sample app, I use ACTION_IMAGE_CAPTURE to take a picture, and I use a FileProvider Uri for that (since file Uri values are going away). Just as you are using EXTRA_STREAM with a Uri, I am using EXTRA_OUTPUT, and so I run into the same Uri-in-an-extra permission problem. And, so, I go through all this mess:
i.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP) {
i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
else {
List<ResolveInfo> resInfoList=
getPackageManager()
.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, outputUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
startActivityForResult(i, CONTENT_REQUEST);
(here, i is my ACTION_IMAGE_CAPTURE Intent)
So, I grant any ACTION_IMAGE_CAPTUREIntentthe rights to work with thisUri` on Android 4.4 and older.
You cannot use the feature without the permission.
If you are using an Android version prior to Marshmallow, you can just add the permission to the Manifest and it won't ask for it again.
But Marshmallow categorized permissions into Normal and Dangerous permissions. You have to ask for dangerous permissions just before the user uses the feature.

Image path from Android camera intent

I am trying to figure out the right way to get a file path from the camera after a picture is taken:
Launch the camera intent. Since I am telling the camera to write to internal storage give it the uri and read permission:
File file = new File(context.getFilesDir(), "picture.jpg");
Uri uri = FileProvider.getUriForFile(getApplicationContext(), "my.app.package.fileprovider", file);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, ACTION_IMAGE_CAPTURE_REQUEST_CODE);
Listen for the camera intent result:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case ACTION_IMAGE_CAPTURE_REQUEST_CODE:
// How do I get the local path to the file here?
break;
}
}
What is the best way to get the file path on camera intent return. Sure I can save off the file path to a member variable before launching the intent, but that seems bad, seems I should get the path from the onActivityResult.
I have tried this (Get Image path from camera intent):
String[] projection = { MediaStore.Images.Media.DATA };
Cursor cursor = managedQuery(MediaStore.Images.Media.INTERNAL_CONTENT_URI,projection, null, null, null);
int column_index_data = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToLast();
String imagePath = cursor.getString(column_index_data);
Only difference being that I am using INTERNAL_CONTENT_URI as I am trying to store the pic internally. If I do this I get an exception:
Error getting data column
java.lang.IllegalArgumentException: column '_data' does not exist
Am I going about this wrong. I want to take a pic and store that pic in internal storage.
EDIT:
One more thought. Should I be storing pics internally? I am right not because of the new android M permissions. I already have to ask the user for Camera permission, if I store pic to external storage, I have to ask the user for permission to write to to external. Lastly if I store the image externally, it is readable by all applications on the device that can read from external storage. This may be a privacy issue for my app.
What is the best way to get the file path on camera intent return
You specified the file path in EXTRA_OUTPUT. Either the camera put the photo there, or it didn't. If it did, then you already know the file path: new File(context.getFilesDir(), "picture.jpg"). That should be the case the vast majority of the time.
Some camera apps are buggy and will ignore EXTRA_OUTPUT. In those cases:
You can see if the camera app returned a Uri in the Intent passed to onActivityResult(), then use ContentResolver and openInputStream() to begin the process of copying the image to your desired location, or
You can see if the "data" extra exists, in which case that's a Bitmap thumbnail that you can save to your desired location.
In all of these cases, the file path is what you specified; it is merely a question of whether or not it takes additional work for you to get the image there.
Sure I can save off the file path to a member variable before launching the intent, but that seems bad
I have no idea why you would think that.
seems I should get the path from the onActivityResult
You are welcome to believe whatever you want. Camera app developers are hopefully reading the documentation for ACTION_IMAGE_CAPTURE. That documentation does not state that the camera app has to return anything if you provide EXTRA_OUTPUT. Hence, many camera apps will not return anything.
UPDATE based on edit:
Should I be storing pics internally?
Um, you already are.
if I store pic to external storage, I have to ask the user for permission to write to to external
That depends on where you are writing. getExternalFilesDir(), getExternalCacheDir(), and kin from Context do not require a permission on Android 4.4+. The methods you call on Environment for external storage locations (e.g., getExternalStoragePublicDirectory()) do require WRITE_EXTERNAL_STORAGE, which is a dangerous permission and would need to be requested at runtime.

Categories

Resources