Deleting own app's created Image throws SecurityException - java

I've a Bitmap which converted into URI by doing something like this
fun bitmapToUri(ctx: Context, bitmap: Bitmap): Uri {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, ByteArrayOutputStream())
val fileName = "${ctx.getString(R.string.app_name)}-${System.currentTimeMillis()}"
val path = MediaStore.Images.Media.insertImage(ctx.contentResolver, bitmap, fileName, "blah blah")
return Uri.parse(path)
}
So, somewhere I've heard that If your application created some media in my case IMAGE.
You can delete without requiring any permission after API LEVEL - 30 ( Android 11 ). However I've added permission in Manifest
So I'm deleting a file or better Image from URI like this ( Let me know If I can also delete in other ways. Target SDK - 30)
fun deleteUri(context: Context, uri: Uri) {
val contentResolver = context.contentResolver
contentResolver.delete(uri, null, null)
}
Calling this function in some secret place just kidding in MainActivity like this -
uri = bitmapToUri(this#MainActivity, bitmap)
...
// I assure you uri is not null
coroutineScope.launch {
deleteUri(this#MainActivity, uri)
}
...
I'm getting a SecurityException :(
Writing exception to parcel
java.lang.SecurityException: com.example.myapp has no access to content://media/external/images/media/1000000077
at com.android.providers.media.MediaProvider.enforceCallingPermissionInternal(MediaProvider.java:9959)
at com.android.providers.media.MediaProvider.enforceCallingPermission(MediaProvider.java:9856)
at com.android.providers.media.MediaProvider.deleteInternal(MediaProvider.java:5878)
at com.android.providers.media.MediaProvider.delete(MediaProvider.java:5767)
.... and some more
I've also added file provider in Manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"
tools:replace="android:resource" />
</provider>
P.S - I'm testing in Emulator - Pixel 6 API 33, also new on on StackOverflow so please be easy
If you have any question don't hesitate to shoot comment :)

Related

Android programatically stored image has invalid format on PC

I'm trying to take a picture and save it following Android Documentation.
The main difference is that my code is extending Fragment, not Activity. And the fact that I'm storing pictures on the private app folder instead of the public external storage.
My Code:
private void dispatchTakePictureIntent() {
PackageManager packageManager = getActivity().getPackageManager();
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(packageManager) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Log.d(TAG, "Error occurred while creating the File:"+ex.toString());
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(getActivity(),
"com.eric.nativetoolkit.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
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 = getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpeg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
return image;
}
This is basically cause every time I try to save pictures on the external Storage, AndroidStudio logcat shows the error "Error occurred while creating the File" due to permisions, I've the following permisions in my AndroidManifest.xml, and I also have a method to request it, but nothing works.
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eric.nativetoolkit">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<application
android:allowBackup="true"
android:supportsRtl="true">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.eric.nativetoolkit.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
</application>
</manifest>
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="." />
</paths>
But the main problem is that even correctly storing pictures inside my package/files/Pictures directory, when I try to see the image plugging my device onto the computer and navigating to the folder, it shows the message (in spanish) "Can't open file":
What I can not understand is why in my device I can correctly visualize the image. I've tried changing format (JPG, JPEG, PNG...) but no diference.
Checking if that FLAG_GRANT works. But why I need that flag only for external? And where is the relation with the fact that I can not visualize my pictures on Desktop?
You are launching a third-party camera app via ACTION_IMAGE_CAPTURE. It has no rights to write to the location specified by your FileProvider-supplied Uri. Adding FLAG_GRANT_WRITE_URI_PERMISSION tells Android that your app wishes to give write access to that location to the camera app.
Without that permission, the camera app will fail with some sort of error. You will still have a file, since you are using File.createTempFile() to create an unnecessary empty file before you try ACTION_IMAGE_CAPTURE. If you look at the file size via your desktop file manager, you should see that it is 0 bytes. A 0-byte file is not a valid image, which is why the desktop cannot display it.

How to attach a pdf to intent to send (to email, dropbox, etc) in android

Hey so I save my pdf in external data storage. Eg:
Environment.getExternalStorageDirectory().getPath() + "/file.pdf"
Then, I try to attach it to intent to send:
File attachment = this.getFileStreamPath(fileDirectory + "/" + fileName);
Uri uri = Uri.fromFile(attachment);
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setDataAndType(Uri.parse("mailto:"), "text/plain"); // I have also tried "application/pdf"
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Calc PDF Report");
emailIntent.putExtra(Intent.EXTRA_TEXT, " PDF Report");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(emailIntent, "Send mail..."));
finish();
and im getting the error:
Caused by: java.lang.IllegalArgumentException: File /storage/emulated/0/file.pdf contains a path separator
I am thinking it is something wrong with were I am saving my file, but can't find any examples that are up-to-date.
To share a file as an email attachment using intent, you need to use a FileProvider.
/**
* Generate file content and returns uri file
*/
public static Uri generateFile(Context context) {
File pdfDirPath = new File(context.getFilesDir(), "pdfs");
pdfDirPath.mkdirs();
File file = new File(pdfDirPath, "attachment.pdf");
file.deleteOnExit();
Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".file.provider", file);
FileOutputStream os = null;
try {
Logger.info("Generate file " + file.getAbsolutePath());
os = new FileOutputStream(file);
document.writeTo(os);
document.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
return uri;
}
private void share(Context context) {
Uri uri = generateFile(context);
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
emailIntent.putExtra(EXTRA_SUBJECT, "Send something");
emailIntent.putExtra(Intent.EXTRA_TEXT, "You receive attachment");
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(emailIntent);
}
In your app add the file provider definition:
AndroidManifest.xml
<application
android:name=".DemaApplication"
android:allowBackup="false"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.file.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
...
</application>
provider_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="internal_files"
path="/"/>
<!--<external-path name="external_files" path="./files"/>-->
</paths>
At last but not least, you need to specify the file provider path (where are your files). I hope this helps. Here, the official documentation about how to send email and attachment with intent.
Problem #1: getFileStreamPath() does not support subdirectories and is not related to external storage, where your file resides.
Problem #2: Uri.fromFile() will not work on Android 7.0, as your app will crash with a FileUriExposedException. To fix this and Problem #1, use FileProvider to set up a content Uri that you can use for EXTRA_STREAM.
Problem #3: ACTION_SEND does not use a "data" Uri (i.e., your "mailto:" should not be there).
Problem #4: The MIME type of a PDF is not text/plain — as your comment notes, use application/pdf.
Problem #5: getExternalStorageDirectory() is deprecated on Android 10 and higher, and you will not be able to write files there. Consider using getExternaFilesDir(null) (called on a Context) for a better location that works without permissions and on more Android OS versions.
can't find any examples that are up-to-date
The documentation covers the use of ACTION_SEND.

How to capture image and send its Uri directly to attachment via Email

I have Ui in which I have to capture an image and onActivityResult I have to send it to directly to email, for that I have tried every possible solution on stack but failed every time and its give me couldn't attach error, after a long time of searching and implementing I have found something to check a , If I can read the file or not like this file.canRead() it always gives me false. Any solution would be appreciated.
private fun sendEmail(liscence: String, desc: String) {`<br>
val emailIntent = Intent(Intent.ACTION_SEND)`<br>
val to = arrayOf("info#gmail.com")`<br>
emailIntent.putExtra(Intent.EXTRA_EMAIL, to)`
emailIntent.type = "text/plain";
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// attachment Uri comes through camera
emailIntent.putExtra(Intent.EXTRA_STREAM, imagePath)
// the mail subject
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Report")
//email body
val body = "${liscence} \n ${desc}"
emailIntent.putExtra(Intent.EXTRA_TEXT, body)
//need this to prompts email client only
emailIntent.type = "message/rfc822";
startActivity(Intent.createChooser(emailIntent, "Send email using..."))
}
Did you add READ_EXTERNAL_STORAGE permission to your app and ask this permission from the user in android SDK greater than 23?
Also, if you save your attachment as private, it's not readable for mail. This link explains this more.
Here After a lot of research, I have found the solution in the Android documentation
https://developer.android.com/reference/android/support/v4/content/FileProvider.html#GetUri
Here is my final code for sharing a captured image to Gmail.
this code will go straight to Manifest file you have to use authorities as a valid authority provider.
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/picker_provider_paths" />
</provider>
XML file for the provider:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="image_picked" path="picked/"/>
<external-path name="*" path="Pictures/"/>
</paths>
Function to SendImage as an attachment.
private fun sendEmail() {
val contentUri =
FileProvider.getUriForFile(context!!,
"yourPackageAndThen.com.mydomain.fileprovider",
File(PATH OF IMAGE or FILE ));
val emailIntent = Intent(Intent.ACTION_SEND)
//need this to prompts email client only
emailIntent.type = "message/rfc822";
val to = arrayOf("EMAIL ID TO SEND")
emailIntent.putExtra(Intent.EXTRA_EMAIL, to)
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
emailIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
// attachment Uri comes through camera
emailIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
// the mail subject
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Report")
//email body
emailIntent.putExtra(Intent.EXTRA_TEXT, MESSAGEforBody)
startActivity(Intent.createChooser(emailIntent, "Send email using..."))
}

FileProvider: Failed to find configured root

I have been trying to share a pdf file, I set up the FileProvider like this:
On the main manifest xml:
<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/file_paths" />
</provider>
The res/xml/file_paths.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="my_files" path="." />
</paths>
And in my code I am trying the following:
String path = root.getAbsolutePath() + "/file.pdf";
final Uri data = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID+".fileprovider", new File(path));
getApplicationContext().grantUriPermission(getApplicationContext().getPackageName(), data, Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent intent = new Intent(Intent.ACTION_VIEW).setDataAndType(data, "application/pdf").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
getApplicationContext().startActivity(intent);
Returns error:
Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/file.pdf
The documentation for FileProvider says that <files-path>:
Represents files in the files/ subdirectory of your app's internal storage area. This subdirectory is the same as the value returned by Context.getFilesDir().
Your file is not in internal storage. It is in external storage. For that, you need an <external-path> element, not a <files-path> element.
The accepted answer is correct, but he didn't specify the API to use, so here it is.
val imagePath = File(
Environment.getExternalStorageDirectory().path +
File.separator, "Your folder name"
)
val newfile = File(imagePath, "Your file name")
val imageUri = FileProvider.getUriForFile(
context,
"com.example.domain.provider",
newfile
)
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "image/jpg" // set file type
shareIntent.putExtra(
Intent.EXTRA_STREAM,
imageUri
)
context.startActivity(Intent.createChooser(shareIntent, "Share Status Saver Image"))
Happy Coding!

Sharing a file in the raw folder with a FileProvider

I'm trying to pass an image that resides in the res/raw directory of my app along with a share intent.
I followed the process described in the FileProvider docs, and here's my code:
AndroidManifest.xml
<application ...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths" />
</provider>
</application>
res/xml/paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="shared" path="./"/>
</paths>
The code in my activity:
String shareToPackage = ...
File imageFile = new File(context.getFilesDir().getPath() + "/image");
if (!imageFile.exists()) { // image isn't in the files dir, copy from the res/raw
final InputStream inputStream = context.getResources().openRawResource(R.raw.my_image);
final FileOutputStream outputStream = context.openFileOutput("image", Context.MODE_PRIVATE);
byte buf[] = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
imageFile = new File(context.getFilesDir().getPath() + "/image");
}
if (!imageFile.exists()) {
throw new IOException("couldn't find file");
}
final Uri uri = Uri.fromFile(imageFile);
context.grantUriPermission(shareToPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/png");
intent.putExtra(Intent.EXTRA_TEXT, "here's the image");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setPackage(shareToPackage);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
The above does not work as the file that I get in the other app isn't accessible:
java.io.FileNotFoundException: FILE_PATH: open failed: EACCES
(Permission denied)
Any idea what I'm doing wrong here?
Thanks.
Get rid of the path attribute in <files-path>, as it is not needed here, since you are serving everything from getFilesDir().
Do not use string concatenation when creating File objects. Replace:
new File(context.getFilesDir().getPath() + "/image.png");
with:
new File(context.getFilesDir().getPath(), "image.png");
Most importantly, do not use Uri.fromFile(). Use FileProvider.getUriForFile(). As it stands, you are going through all this work to set up FileProvider, then you do not use the FileProvider for making the content available to the other app.
Or, get rid of all of this, and use my StreamProvider, which can serve a raw resource directly.
Or, write your own ContentProvider that serves the raw resource directly.
#nitzan-tomer, see https://stackoverflow.com/a/33031091/966789
What Are Runtime Permissions?
With Android 6.0 Marshmallow, Google introduced a new permission model that allows users to better understand why an application may be requesting specific permissions. Rather than the user blindly accepting all permissions at install time, the user is now prompted to accept permissions as they become necessary during application use.

Categories