Picking an image from gallery gives error Android N - java

I am having trouble with retrieving the Uri from an intent in Android N.
As far as I know on Android 24 and above to get an external Uri you need FileProvider declared in Manifest. That's all done and it works with the camera, but when I try to get an image from the gallery I get an error in onActivityResult data.getData();
These are a few samples of my code:
public void getPictureFromGallery(){
picUriCar = null;
try {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, PIC_SELECT);
} catch (ActivityNotFoundException e) {
Toast.makeText(MainActivity.this, getResources().getString(R.string.text_error_no_gallery_app),
Toast.LENGTH_SHORT).show();
}
}
And onActivityResult:
else if(requestCode == PIC_SELECT){
picUriCar = data.getData();
if (picUriCar != null){
performCrop();
}
}
As far as I know data.getData() returns a Uri and this works ok on Marshmallow, but on a Nougat phone i get this error:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=5, result=-1, data=Intent {
dat=content://com.android.externalstorage.documents/document/4996-1EFF:DCIM/100ANDRO/DSC_0004.JPG
flg=0x1 }} to activity
{com.company.example/com.company.example.MainActivity}:
java.lang.SecurityException: Uid 10246 does not have permission to uri
0 #
content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG
at android.app.ActivityThread.deliverResults(ActivityThread.java:4267)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6361)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
Caused by: java.lang.SecurityException: Uid 10246 does not have permission to uri 0 #
content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG
at android.os.Parcel.readException(Parcel.java:1683)
at android.os.Parcel.readException(Parcel.java:1636)
at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3213)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1525)
at android.app.Activity.startActivityForResult(Activity.java:4235)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767)
at android.app.Activity.startActivityForResult(Activity.java:4194)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754)
at com.company.example.MainActivity.performCrop(MainActivity.java:1654)
at com.company.example.MainActivity.onActivityResult(MainActivity.java:1534)
at android.app.Activity.dispatchActivityResult(Activity.java:6928)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4263)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310) 
at android.app.ActivityThread.-wrap20(ActivityThread.java) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628) 
at android.os.Handler.dispatchMessage(Handler.java:110) 
at android.os.Looper.loop(Looper.java:203) 
at android.app.ActivityThread.main(ActivityThread.java:6361) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
My question is:
How do I pass data.getData() uri to picUriCar without any errors?

Use this code to create new intent to choose image:
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType(image/*);
startActivityForResult(Intent.createChooser(intent, ""), Constants.SELECT_PICTURE);
Use this code in onActivityResult:
if (resultCode == Activity.RESULT_OK) {
if (requestCode == Constants.SELECT_PICTURE) {
Uri selectedImageUri = data.getData();
try {
if (isNewGooglePhotosUri(selectedImageUri)) {
resultFile = getPhotoFile(selectedImageUri);
} else {
resultFile = getFilePathForGallery(selectedImageUri);
}
if (resultFile == null) {
//error
return;
}
} catch (Exception e) {
e.printStackTrace();
//error
return;
}
}
}
}
Also here is some usefull function that i use in my code:
private File getFilePathForGallery(Uri contentUri) {
String path = null;
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
if (cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
path = cursor.getString(column_index);
}
cursor.close();
return new File(path);
}
public static boolean isNewGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
}
private File getPhotoFile(Uri selectedImageUri) {
try {
InputStream is = mActivityInstance.getContentResolver().openInputStream(selectedImageUri);
if (is != null) {
Bitmap pictureBitmap = BitmapFactory.decodeStream(is);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
pictureBitmap.compress(Bitmap.CompressFormat.JPEG, 80, bytes);
File output = new File(FileManager.getImageCacheDir(mActivityInstance), System.currentTimeMillis() + ".jpg");
output.createNewFile();
FileOutputStream fo = new FileOutputStream(output);
fo.write(bytes.toByteArray());
fo.close();
return output;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Functions from FileManager class:
private static File getCacheDir(Context context) {
File cacheDir = context.getExternalFilesDir(null);
if (cacheDir != null) {
if (!cacheDir.exists())
cacheDir.mkdirs();
} else {
cacheDir = context.getCacheDir();
}
return cacheDir;
}
public static File getImageCacheDir(Context context) {
File imageCacheDir = new File(getCacheDir(context), "cache_folder");
if (!imageCacheDir.exists())
imageCacheDir.mkdirs();
return imageCacheDir;
}
Also you need to create new xml file in your xml folder:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>
and then add new provider to manifest file:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/your_xml_file" />
</provider>

I did a few checks and as always the solution was simpler that I expected. This is my performCrop function. I have two options when clicking on a View. Either get the image from the gallery or capture it with the camera. I remembered that the gallery option problem occurred only after I fixed the camera problem. So the issue might be somewhere within the crop code. So this is what I did:
public void performCrop(boolean cameraShot){
try {
Intent cropIntent = new Intent("com.android.camera.action.CROP");
//indicate image type and Uri
//cropIntent.setDataAndType(picUri, "image");
cropIntent.setDataAndType(picUriCar, "image/*");
//set crop properties
cropIntent.putExtra("crop", "true");
//indicate aspect of desired crop
cropIntent.putExtra("aspectX", 2);
cropIntent.putExtra("aspectY", 1);
//indicate output X and Y
cropIntent.putExtra("outputX", 1024);
cropIntent.putExtra("outputY", 512);
cropIntent.putExtra("scale", true);
cropIntent.putExtra("return-data", false);
Uri uri;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !cameraShot) {
uri = Uri.fromFile(croppedFileCar);
}else{
String authority = "com.company.example.provider";
uri = FileProvider.getUriForFile(
MainActivity.this,
authority,
croppedFileCar);
cropIntent.setClipData(ClipData.newRawUri( "", uri ) );
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
Please notice - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
!cameraShot)
So basically the logic behind it is that if the image is not provided by the camera (which is determined in onActivityResult) or API is below 24, execute the line:
uri = Uri.fromFile(croppedFileCar);
Otherwise use the FileProvider. Before I did these changes the app crashed when picking an image from the gallery. I have no idea why it works now, but if someone knows the answer, I would be happy to hear it.

Related

Open PDF after download with DownloadManager and FileProvider on Android Q (10)

targetSdkVersion: 30
In our App we have a feature, where we download files (mostly pdf) to the public download folder and start an intent afterwards to open it. Our code works fine for android apps with api >= 28 and >= 30. Just our app on Android 10 (sdkVersion 29) will try to open the document and instantly closes the activity that tried to display the pdf. The logcat shows following error:
22-03-17 14:23:42.486 12161-15168/? E/DisplayData: openFd: java.io.FileNotFoundException: open failed: EACCES (Permission denied)
22-03-17 14:23:42.486 12161-15168/? E/PdfLoader: Can't load file (doesn't open) Display Data [PDF : download.pdf] +ContentOpenable, uri: content://com.example.fileprovider/Download/download.pdf
If I grant the files permission in the app settings, it will work flawlessly but if I understood the android documentation correctly this should not be necessary since Android 10. Especially because there are no problems on Android 11 and Android 12 devices that do not have this permission. On all Android versions the file will be downloaded correctly and the user could manually open it from the download section of his device.
This is the Android Manifest section for the fileprovider
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
The filepaths XML file
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="external_files"
path="." />
<external-path
name="Download"
path="Download/"/>
<files-path
name="files"
path="." />
<external-cache-path
name="external_cache"
path="." />
</paths>
This is the code with the DownloadManager usage to download the files
public static long downloadFile(Context context, String url, String fileName) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
try {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle(fileName)
.setDescription(Localization.getStringClient("file_download_progress"))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
return downloadManager.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
}
return -1;
}
The broadcast receiver that listens on the download progress
public class DownloadBroadcastReceiver extends BroadcastReceiver {
private long downloadId = -2;
public void setDownloadId(long downloadId) {
this.downloadId = downloadId;
}
#Override
public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (id == downloadId) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor c = downloadManager.query(query);
if (c != null) {
c.moveToFirst();
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
String mediaType = c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
Uri fileUri = Uri.parse(uriString);
DownloadUtils.openFile(context, fileUri, mediaType);
} else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
}
c.close();
}
}
}
}
The code to open the file. Only on Android 10 the app closes the activity instantly again.
public static void openFile(Context context, Uri fileUri, String mediaType) {
File file = new File(fileUri.getPath());
Uri uri = FileProvider.getUriForFile(
context,
context.getApplicationContext().getPackageName() + ".fileprovider",
file
);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mediaType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Toast.makeText(context, Localization.getStringClient("open_file_no_activity"), Toast.LENGTH_SHORT).show();
}
}
And a download call in the looks like this
#Override
public void startPDFDownload(String pdfDownloadUrl, String fileName) {
long downloadId = DownloadUtils.downloadFile(requireContext(), pdfDownloadUrl, fileName);
if (downloadId > -1) {
DownloadBroadcastReceiver receiver = new DownloadBroadcastReceiver();
receiver.setDownloadId(downloadId);
requireContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
}
I think I have a false understanding of how file handling works in Android 10 but I do not know where I have to adjust the code or configuration. Help is really appreciated. Currently as a workaround we ask for the permission for WRITE_EXTERNAL_STORAGE to open downloaded files on Android 10. But I would prefer to do it the right way.
Solution:
I adjusted the BroadcastReceiver to the following code. I removed the LOCAL_URI from the cursor and used the URI from the DownloadManager method.
public class DownloadBroadcastReceiver extends BroadcastReceiver {
private long downloadId = -2;
public void setDownloadId(long downloadId) {
this.downloadId = downloadId;
}
#Override
public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (id == downloadId) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor c = downloadManager.query(query);
if (c != null) {
c.moveToFirst();
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String mediaType = c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
DownloadUtils.openFile(context, downloadManager.getUriForDownloadedFile(id), mediaType);
} else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
}
c.close();
}
}
}
}
And I adjusted the method to open the file with the uri to following code. I removed the FileProvider code and used the uri from the DownloadManager.
public static void openFile(Context context, Uri fileUri, String mediaType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(fileUri, mediaType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Toast.makeText(context, Localization.getStringClient("open_file_no_activity"), Toast.LENGTH_SHORT).show();
}
}
The code of the other methods remains the same.
You should not use FileProvider to obtain an uri for your file.
You can get an uri from DownloadManager and use it to serve your file.
Code would be the same for all Android versions.
Not a single permission needed.

Save image using MediaStore, saved with wrong name and extension

I'm trying to use MediaStore API to save an image but it always save the file with this name
1637955326427.jpg and the size of the file is 0 B.
this is the code that i use:
OutputStream fos;
ContentResolver resolver = getBaseContext().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "fileName.jpg");
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
try {
fos = resolver.openOutputStream(Objects.requireNonNull(imageUri));
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(),list.get(0).getDocfile().getUri());
if(bitmap!=null){
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
list.get(0).getDocFile() return a DocumentFile also i tested this on API 28
Thanks in advance
NOTICE this image already exist on the External Storage, just need to copy it.
EDIT: I delete this code fos = resolver.openOutputStream(imageUri); was duplucate. I do realy apologize. the file work fine but steal wrong name.
First lets take a look at getting write permissions for an existing image. Please note that the photopath does not necessarily exist yet but should point to the location you want your file to be saved.
if (!requestPhotoWritePermission(this, getImageUriFromFS(this, new File(photoPath)))) {
return;
} else {
takePicture();
}
This will form an overwrite request with the default camera
public static Uri getImageUriFromFS(Context c, File file) {
long id = getFilePathToImageID(file, c);
Uri fromUri = ContentUris.withAppendedId( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,id);
return fromUri;
}
public static long getFilePathToImageID(File imagePath, Context context)
{
Uri mainUri;
Cursor cursor1 = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media._ID},
MediaStore.Images.Media.DATA + "=? ",
new String[]{imagePath.getAbsolutePath()}, null);
long id = 0;
if (cursor1 != null && cursor1.moveToFirst()) {
id = cursor1.getLong(cursor1.getColumnIndex(MediaStore.MediaColumns._ID));
cursor1.close();
}
return id;
}
public static boolean requestPhotoWritePermission(Activity activity, Uri fromUri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
boolean hasPermission = true;
if (activity.checkUriPermission(fromUri, Binder.getCallingPid(), Binder.getCallingUid(),
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
hasPermission = false;
}
List<Uri> uriList = new ArrayList<>();
uriList.add(fromUri);
if (!hasPermission) {
PendingIntent pi = MediaStore.createWriteRequest(activity.getContentResolver(), uriList);
try {
activity.startIntentSenderForResult(pi.getIntentSender(), REQUEST_OVERWRITE_PERMISSION_CODE, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return false;
}
return true;
}
return true;
}
This will persist the permission to overwrite or create a new picture via the camera intent
Then in your activity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 53) {
if (resultCode == RESULT_OK) {
//you'll want to take your picture here and possibly store the uri to your photo for later retrieval
takePicture();
}
}
This will take a picture if the permission request is accepted
To Take a picture using the default intent with a provider:
Declare your provider in the manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
1.5: xml File paths file:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="internal_images"
path="files/Pictures" />
<external-files-path
name="internal_images_alternate"
path="Pictures" />
<external-path
name="external"
path="Documents" />
<external-files-path
name="external_files"
path="Documents" />
<cache-path
name="cache"
path="Documents" />
<external-cache-path
name="external_cache"
path="Documents" />
<files-path
name="files"
path="Documents" />
</paths>
The above will give you permission to save to the documents folder. Replace as required.
Utilize the provider to take the picture
public static File dispatchTakePictureIntent(Activity activity, String photoPath) {
Intent takePictureIntent = getCameraIntentWithUpdatedPackages(activity);
Uri photoURI = Uri.fromFile(new File(photoPath));
Uri photoUri = FileProvider.getUriForFile(activity.getApplicationContext(), activity.getApplicationContext().getPackageName() + ".provider", new File(photoPath));
activity.grantUriPermission(photoURI.getAuthority(), photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
//disable strict mode policies
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
activity.startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
return new File(photoPath);
return null;
}
EDIT:
I forgot something
public static Intent getCameraIntentWithUpdatedPackages(Context context) {
List<ResolveInfo> resolveInfo = new ArrayList<>();
final Intent capturePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
PackageManager pm = context.getPackageManager();
resolveInfo = pm.queryIntentActivities(capturePhoto, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// For Android 11 we need to add specific camera apps
// due them are not added during ACTION_IMAGE_CAPTURE scanning...
// resolveInfo.addAll(getCameraSpecificAppsInfo(context));
}
capturePhoto.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
return capturePhoto;
}
EDIT: Update by the name (in onActivityResult after requesting an overwrite permission)
Uri fromUri = MediaUtils.getImageUriFromFS(this, new File(from));
ContentResolver contentResolver = getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
contentResolver.update(fromUri, contentValues, null, null);
contentValues.clear();
contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, new File(to).getName());
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
contentResolver.update(fromUri, contentValues, null, null);
I delete this code fos = resolver.openOutputStream(imageUri); was duplicate. the image work fine but steal wrong name.
About the Wrong name and extension, i think that MediaStore doesn't work fine with Android 9 and lower, because i tested the same code on Android 11 and it's working fine

Android Application Crashes while Capturing Full Size Picture

I am developing an Android Application that takes a picture and saves a full-size picture onto the device. I have mostly taken help from developers android site. When I run my code and try to take the picture the application crashes instantly. The code I am using is as follows:
static final int REQUEST_IMAGE_CAPTURE = 1;
static final int REQUEST_TAKE_PHOTO = 1;
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
System.out.println("Error in Dispatch Take Picture Intent");
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
//Bundle extras = data.getExtras();
//Bitmap imageBitmap = (Bitmap) extras.get("data");
//imageView.setImageBitmap(imageBitmap);
Toast.makeText(getApplicationContext(),"Working",Toast.LENGTH_LONG).show();
}
}
String currentPhotoPath;
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
return image;
}
private View.OnClickListener onCamClick() {
return new View.OnClickListener() {
#Override
public void onClick(View v) {
System.out.println("Starting Camera Activity");
//dispatchTakePictureIntent();
//Toast.makeText(getApplicationContext(),"Working",Toast.LENGTH_LONG).show();
dispatchTakePictureIntent();
}
};
}
When I run my code I get the following error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.zaeem.tia, PID: 15468
java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/com.zaeem.tia/files/Pictures/JPEG_20200301_003227_1043176843378455577.jpg
at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:739)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:418)
at com.zaeem.tia.app.activities.MainActivity.dispatchTakePictureIntent(MainActivity.java:121)
at com.zaeem.tia.app.activities.MainActivity.access$000(MainActivity.java:40)
at com.zaeem.tia.app.activities.MainActivity$1.onClick(MainActivity.java:164)
at android.view.View.performClick(View.java:7339)
at android.widget.TextView.performClick(TextView.java:14221)
at android.view.View.performClickInternal(View.java:7305)
at android.view.View.access$3200(View.java:846)
at android.view.View$PerformClick.run(View.java:27787)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7076)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
filepath.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Android/data/com.zaeem.tia.app.activities/files/Pictures" />
</paths>
Please advise regarding this matter.
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android.fileprovider",
photoFile);
Here, you are saying that the authority string for your FileProvider is com.example.android.fileprovider. Based on the error, that is not what you have on the <provider> element in the manifest.
add this in your manifest.xml (after ):
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="android.getqardio.com.gmslocationtest"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
Create provider_paths on Resources/xml, and add this content:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="share"
path="external_files"/>
</paths>
Activity:
File imagePath = new File(getFilesDir(), "external_files");
imagePath.mkdir();
File imageFile = new File(imagePath.getPath(), "test.jpg");
// Write data in your file
Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);
Intent intent = ShareCompat.IntentBuilder.from(this)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.setAction(Intent.ACTION_VIEW) //Change if needed
.setDataAndType(uri, "image/*")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);

BitmapFactory: Unable to decode stream: java.io.FileNotFoundException:open failed: EACCES (Permission denied) on Android Q

I granted permissions but BitmapFactory.decodeFile() returns this error:
E/BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/DCIM/Camera/IMG_20200130_165131.jpg: open failed: EACCES (Permission denied)
I want to take the last image from the gallery and save it to another file. I tested it on the Android P test device it was running on. But it returns this error in android emulator with android version Q (API level 29). What's wrong with that?
These are my code snippets:
writeStoragePermissionGranted();
BitmapFactory.Options options = null;
Bitmap bm = null;
String[] projection = new String[]{
MediaStore.Images.ImageColumns._ID,
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.MIME_TYPE,
MediaStore.Images.ImageColumns.DISPLAY_NAME,
};
final Cursor cursor = this.getContentResolver()
.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null,
null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC");
if (cursor.moveToFirst()) {
final ImageView imageView = (ImageView) findViewById(R.id.pictureView);
String imageLocation = cursor.getString(1);
File imageFile = new File(imageLocation);
if (imageFile.exists()) {
options = new BitmapFactory.Options();
options.inSampleSize = 2;
try {
bm = BitmapFactory.decodeFile(imageLocation, options);
} catch(Exception e) {
e.printStackTrace();
}
}
imageView.setImageBitmap(bm);
try {
saveImage(bm,"NAME" );
} catch (IOException e) {
e.printStackTrace();
}
}
writeStoragePermissionGranted() function:
public void writeStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
startPeriodicRequest();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Constants.REQUEST_CODE_WRITE_EXTERNAL_STORAGE_PERMISSION);
}
} else {
return;
}
}
and manifest permissions:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
I know there are many question about that. But no one worked on the android emulator version Q. On the other hand, this code works on android pie (API level 28)
Adding android:requestLegacyExternalStorage="true" to the manifest's application block could actually solve the issue!
On Android 10, they introduced the concept of "Scoped Storage" and this way, you no longer can OPEN a image using its path. You can get more info HERE.
So now you have to decode it using its ParcelFileDescriptor and its Uri.
You can:
final Cursor cursor = this.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC");
if (cursor.moveToFirst()) {
final ImageView imageView = (ImageView) findViewById(R.id.pictureView);
if (Build.VERSION.SDK_INT >= 29) {
// You can replace '0' by 'cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)'
// Note that now, you read the column '_ID' and not the column 'DATA'
Uri imageUri= ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getInt(0));
// now that you have the media URI, you can decode it to a bitmap
try (ParcelFileDescriptor pfd = this.getContentResolver().openFileDescriptor(mediaUri, "r")) {
if (pfd != null) {
bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
}
} catch (IOException ex) {
}
} else {
// Repeat the code you already are using
}
}
You just need to add android:requestLegacyExternalStorage="true" to Application tag in Manifest.xml .
I hope the following code with help you out.
Manifest.xml
Add following in manifest file:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
Also Add:
Add this in Application tag in Manifest.
android:requestLegacyExternalStorage="true"
Open Gallery to get image:
private void openImagePicker() {
MyDatePicker datePicker = new MyDatePicker();
datePicker.show(getSupportFragmentManager(), "DATE PICK");
}
Launch ActivityForResult:
onActivityResult you will get result object through you will get image raw uri.
ActivityResultLauncher<Intent> galleryResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
imageURi = data.getData();
image.setVisibility(View.VISIBLE);
String path = getPath(imageURi);
File file = new File(path);
if (file.exists()) {
Bitmap myBitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
image.setImageBitmap(myBitmap);
}
}
}
});
Get Absolute path of image:
public String getPath(Uri uri) {
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor = managedQuery(uri, projection, null, null, null);
if (cursor != null) {
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} else return null;
}

Android: Cannot add attachment: `SecurityException when openAssetFileDescriptor`

I am unable to attach my file in native android email or Gmail application.
Gmail gives me this error:
2019-01-17 16:33:17.884 15415-15415/? E/Gmail: Gmail:Error adding attachment
fjd: SecurityException when openAssetFileDescriptor.
That is the code where save the file on storage:
public Uri getUrlFromDrawable(String base64ImageData) {
FileOutputStream fos;
try {
final String pureBase64Encoded = base64ImageData.substring(base64ImageData.indexOf(",") + 1);
final byte[] decodedBytes = Base64.decode(pureBase64Encoded, Base64.DEFAULT);
String filename = "receipt2_" + System.currentTimeMillis() + ".jpg";
File file = new File(reactContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES), filename);
fos = new FileOutputStream(file);
fos.write(decodedBytes);
fos.flush();
fos.close();
Uri contentUri = FileProvider.getUriForFile(getReactApplicationContext(), "com.myapp.fileprovider", file);
return contentUri;
}
return null
}
And here is my function to send the mail:
public void mail(ReadableMap options, Callback callback) {
Intent i = new Intent(Intent.ACTION_SENDTO);
i.setData(Uri.parse("mailto:"));
if (options.hasKey("subject") && !options.isNull("subject")) {
i.putExtra(Intent.EXTRA_SUBJECT, options.getString("subject"));
}
if (options.hasKey("body") && !options.isNull("body")) {
String body = options.getString("body");
i.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(new StringBuilder().append(body).toString())
);
}
if (options.hasKey("recipients") && !options.isNull("recipients")) {
ReadableArray recipients = options.getArray("recipients");
i.putExtra(Intent.EXTRA_EMAIL, readableArrayToStringArray(recipients));
}
if (options.hasKey("receipt") && !options.isNull("receipt")) {
String base64ImageData = options.getString("receipt");
try {
if (base64ImageData != null) {
Uri imageUri = getUrlFromDrawable(base64ImageData);
i.putExtra(Intent.EXTRA_STREAM, imageUri);
}
} catch (Exception e) {
Log.e("ERROR", "Cannot save receipt");
}
}
PackageManager manager = reactContext.getPackageManager();
List<ResolveInfo> list = manager.queryIntentActivities(i, 0);
if (list == null || list.size() == 0) {
callback.invoke("not_available");
return;
}
if (list.size() == 1) {
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
reactContext.startActivity(i);
} catch (Exception ex) {
callback.invoke("error");
}
} else {
Intent chooser = Intent.createChooser(i, "Send Mail");
chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
reactContext.startActivity(chooser);
} catch (Exception ex) {
callback.invoke("error");
}
}
}
Here is the fileprovider.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="Pictures"
path="/" />
</paths>
And here is the provider in `AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.fileprovider"
tools:replace="android:authorities"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
tools:replace="android:resource"
android:resource="#xml/fileprovider" />
</provider>
I managed to fix it by granting permission at runtime like this
List<ResolveInfo> list = manager.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : list) {
String packageName = resolveInfo.activityInfo.packageName;
reactContext.grantUriPermission(packageName, imageUri , Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
If you also add it as ClipData, the permission grant will work successfully:
yourIntent.clipData = ClipData.newUri(context.contentResolver, fileName, contentUri)
The culprit is that Intent.FLAG_GRANT_READ_URI_PERMISSION will work only for Intent.data and Intent.clipData, and not for extras, unless the uri permission grant has been given explictly (inconvenient), or has been given by also adding a ClipData.
Note that your answer also worked, though I find it a little less convenient since for Android 11+, it requires you to declare queries in the AndroidManifest.xml, which might also be a privacy issue in some cases.

Categories