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.
Related
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 :)
I am creating a .xls file and saving it to the Downloads folder. I am trying to send the .xls file as an email attachment. However, when Gmail opens up I get a toast error message that says Couldn't Attach File. This error message is coming from Gmail. I have verified that the file is being created properly. I am not sure why it's not attached to the email.
Here is the code that creates the file and emailIntent.
try{
String nowDateTime = System.currentTimeMillis()+"";
File filePath = new File(Environment.getExternalStorageDirectory()+"/Download/"+"ExcelFile_"+nowDateTime+".xls");
if(!filePath.exists()){
filePath.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
hssfWorkbook.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
Intent emailIntent = new Intent(Intent.ACTION_SEND);
Uri uri = Uri.fromFile(filePath);
Log.e("FilePath", filePath.toString());
Log.e("Uri", uri.toString());
emailIntent.setType("application/excel");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.Email_Subject_Line));
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(emailIntent);
}catch(Exception e){
Log.e("GraphReportDialog", "Creating Excel Sheet File Output Stream Error "+ e.getMessage());
}
Manifest Permissions:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
CommonsWare was correct about using FileProvider. The documentation says Uri.fromFile() is less secure than FileProvider.
Once I added 3 main things, it worked as expected.
The first thing is adding providers in the manifest file:
<provider
android:authorities="com.example.counter"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"
/>
</provider>
The second thing is adding the paths. I created another directory in my res file called xml. I then added an xml file called provider_paths.xml. Here is that code:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path <!-- there are several different types of paths to use see the documentation-->
name="external_files"
path="." /> <!-- consider changing this to more specific path-->
</paths>
Lastly, this is how I implemented it.
String nowDateTime = System.currentTimeMillis()+"";
File filePath = new File(Environment.getExternalStorageDirectory()+"/Download/"+"ExcelFile_"+nowDateTime+".xls");
if(!filePath.exists()){
filePath.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
hssfWorkbook.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
Intent emailIntent = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(this.getContext(),"com.example.counter", filePath);
emailIntent.setType("application/excel");
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.Email_Subject_Line));
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(emailIntent);
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.
I am trying to get the URI for a file like this:
Uri photoURI = FileProvider.getUriForFile(this,
"br.com.[blablabla].sgaconsultor",
createImageFile());
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "registroFoto";
ContextWrapper cw = new ContextWrapper(getApplicationContext());
File directory = cw.getDir("imageDir", Context.MODE_PRIVATE);
return File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
directory
);
}
I have a file provider:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="br.com.[blablabla].blabla"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
And the xml is:
<paths>
<files-path name="br.com.[blablabla].blabla" path="app_imageDir" />
</paths>
But when I run this code I always get this error:
java.lang.IllegalArgumentException: Android: FileProvider IllegalArgumentException Failed to find configured root that contains /data/data/**/files/Videos/final.mp4
But no luck so far... So how can I fix this and get the URI for my file?
Any help is really appreciated!
So how can I fix this and get the URI for my file?
Step #1: Get rid of the useless stuff in the Java code.
Step #2: Get the Java code and the file_paths resource to match, as file_paths says that the file path has to be under getFilesDir() and have app_imageDir in it, and your Java code does not have either of those things.
So, try this:
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "registroFoto";
File directory = new File(getFilesDir(), "app_imageDir");
directory.mkdirs();
return File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
directory
);
}
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.