My app has features to 'Attach File', 'Take Picture', 'Take Video' etc. I was passing a File Uri to a new Intent but am getting the FileUriExposedException in Nougat. Hence have modified the code to use FileProvider. I am getting the Content Uri fine, but I get java.io.FileNotFoundException when I try to read or upload the file/picture/video. Am I setting the permissions wrong? Or do I need to set permissions in another way?
Here is what I am doing:
Android_manifest.xml:
<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/provider_paths"/>
</provider>
provider_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="."/>
<external-path name="external_files" path="."/>
</paths>
MainActivity.java:
/*
* Creating file uri to store image/video
*/
public Uri getOutputMediaFileUri() {
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), Config.IMAGE_DIRECTORY_NAME);
if (!mediaStorageDir.exists()) {
try {
mediaStorageDir.mkdirs();
Log.d(TAG, "Created directory " + mediaStorageDir.getPath());
} catch (SecurityException e) {
e.printStackTrace();
}
}
if (mediaStorageDir != null) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator+ "IMG_1" + ".jpg");
try {
Uri photoURI = FileProvider.getUriForFile(MainActivity.this,BuildConfig.APPLICATION_ID + ".provider", mediaFile);
return photoURI;
} catch (IllegalArgumentException e) {
Log.e("File Selector","The selected file can't be shared: " +mediaFile);
}
}
return null;
}
In shouldOverrideUrlLoading (where I am setting up the Intent):
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
intent.putExtra(Intent.EXTRA_MIME_TYPES,extraMimeTypes);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);
intent.addCategory(Intent.CATEGORY_OPENABLE);
}
try {
startActivityForResult(Intent.createChooser(intent, "Select a File to Upload"),PICKFILE_REQUEST_CODE);
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(getApplicationContext(), "Please install a File Explorer.", Toast.LENGTH_SHORT).show();
}
In onActivityResult:
case PICKFILE_REQUEST_CODE:
if (resultCode == RESULT_OK){
Uri selectedFile = data.getData();
try {
filePath = getPath(this,selectedFile);
} catch (URISyntaxException e) {
e.printStackTrace();
}
System.out.println("PICKFILE_REQUEST " + filePath);
if(filePath != null){
new UploadFileToServer().execute();
}else{
Toast.makeText(getApplicationContext(), " Please select from File Explorer or Gallery ", Toast.LENGTH_SHORT).show();
}
}
In the Android Monitor my output shows
I/System.out: PICKFILE_REQUEST /storage/32CF-12FE/DCIM/Camera/20170711_161710.jpg
which I guess means the content Uri is correct
However, I am unable to upload the file
java.io.FileNotFoundException: /storage/32CF-12FE/DCIM/Camera/20170711_161710.jpg: open failed: EACCES (Permission denied)
Does this mean my permissions are not set correctly? How do I correct this.
Your code looks fine, but instead of getting the file URI through FileProvider, use the file's absolute path. Use your mediaFile and get absolute path by mediaFile.getAbsolutePath().
This will return a string which you can use to read the file.
Related
I am trying to delete images taken through the Camera api and, in principle it does delete them from my application folder but the images are still in the DCIM/camera folder.
I am using an ITOS device with Android version 9.
Here is the code I am using.
manifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
------
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.cameratest.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
#xml/file_paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
And this is how I capture the image:
private void checkPermissions(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, MY_CAMERA_PERMISSION_CODE);
} else {
captureImage();
}
} else {
captureImage();
}
}
private void captureImage() {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (cameraIntent.resolveActivity(getPackageManager()) != null) {
File imageFile = null;
try {
imageFile = getImageFile();
} catch (IOException e) {
Log.d("TAG", "captureImage ERROR: " + e.getMessage());
e.printStackTrace();
}
if (imageFile != null) {
Uri imageUri = FileProvider.getUriForFile(this, Constants.FILE_AUTHORITY_PROVIDER, imageFile);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(cameraIntent, CAMERA_REQUEST);
}
}
private File getImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "IMG_" + timeStamp;
File storageDir = new File(this.getExternalFilesDir(Environment.DIRECTORY_PICTURES),"MisFotos");
if (! storageDir.exists()){
if (! storageDir.mkdirs()){
Log.d("MyCameraApp", "failed to create directory");
return null;
}
}
File image = new File(storageDir.getPath() + File.separator + imageFileName + ".jpg");
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
Log.d("PHOTO_TAG", "getImageFile: " + currentPhotoPath);
return image;
}
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CAMERA_REQUEST && resultCode == Activity.RESULT_OK) {
setPictureToDataBase();
}
}
UPDATED
I save the image path in a database and then retrieve it to delete the file.
And here is the code I use to delete the image in my Picture Adapter
public void deletePicture(Picture picture) {
try {
myDatabase.deletePicture(picture.getUid());
String path = picture.getImage();//storageDir.getPath() + File.separator + imageFileName + ".jpg"
pictures.remove(picture);
notifyDataSetChanged();
if (pictures != null && pictures.size()>0){
listener.onDeleteClick( pictures.size());
}else {
listener.onDeleteClick( 0);
}
File target = new File(path);
if (target.exists()) {
target.delete();
}
else {
Log.d("TAG_DELETE_PICTURE", "ERROR: FILE NOT EXITS");
}
} catch (Exception e) {
Log.d("deletePicture", "ERROR: " + e.getMessage());
}
}
And when I open the device file explorer it is in "MyPhotos" and in DCIM/camera.
same image, two places
It works correctly when deleting the image from the application folder, but when looking in DCIM/camera the images are still there and occupy memory.
Am I doing something wrong, is there a way to delete the image saved in DCIM/camera?
I want to take the photo, save it only in the folder of the application, and not save it anywhere else. Is there any other way to do this?
Am I doing something wrong
You are launching a camera app via ACTION_IMAGE_CAPTURE. There are tens of thousands of Android device models. There will be dozens of different pre-installed camera apps across those device models, and users can install other camera apps from the Play Store and elsewhere.
What those camera apps do is up to their developers.
In your case, the camera app that you happen to be using is both saving the photo in its normal place and making a copy in the location identified by EXTRA_OUTPUT. Few camera apps will do this, but it is perfectly legitimate for a camera app to behave that way.
I want to take the photo, save it only in the folder of the application, and not save it anywhere else. Is there any other way to do this?
Do not use ACTION_IMAGE_CAPTURE. Instead, use the camera APIs directly or via a wrapper library (Google's CameraX, FotoApparat, CameraKit-Android, etc.).
My application has read and write permissions to the download folder. How to call the default application to open a file and give it the right to read the file?
When use this code, the default application opens, but cannot access the file:
public void openFile(String fileName)
{
String mimeType = URLConnection.guessContentTypeFromName(fileName);
Intent newIntent = new Intent(Intent.ACTION_VIEW);
Uri myUri = Uri.parse(fileName);
newIntent.setDataAndType(myUri, mimeType);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
newIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
try {
this.startActivity(newIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, "No handler for this type of file.", Toast.LENGTH_LONG).show();
}
}
Acrobat reader, for example, reports that there is no access to the file.
Only the GoogleFoto app when opened asked permission and successfully opened the picture.
Thanks to CommonsWare
android/AndroidManifest.xml
<!-- file provider for open attached files-->
<provider android:name="android.support.v4.content.FileProvider" android:authorities="XXX.XXXXXXXXXXXX" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/file_provider_paths"/>
</provider>
android/res/xml/file_provider_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="/" />
<files-path name="files" path="/" />
</paths>
MainActivity.java
public String openFile(String filePath)
{
File file = new File(filePath);
if (!file.exists())
return "file not exist";
Uri uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);
String mimeType = URLConnection.guessContentTypeFromName(filePath);
Intent newIntent = new Intent(Intent.ACTION_VIEW);
newIntent.setDataAndType(uri, mimeType);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
newIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
try {
this.startActivity(newIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, "No handler for this type of file.", Toast.LENGTH_LONG).show();
return "No handler for this type of file.";
}
return "";
}
I am calling this method when capturing image from the nought.
private void CallCameraFeature() {
Intent cameraOpeningIntent = new
Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String fileName = EmpConstants.startImgName +
new SimpleDateFormat(EmpConstants.PhotoFileFormat,
Locale.US).format(new Date());
File imgFile = new File(mContext.getFilesDir(), "images");
File outFile = new File(imgFile, fileName + ".jpg");
Uri photoURi = FileProvider.getUriForFile(mContext,
BuildConfig.APPLICATION_ID + ".provider", outFile);
cameraOpeningIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURi);
startActivityForResult(cameraOpeningIntent, REQUEST_IMAGE_CAPTURE);
}
}
I have created xml file in
values -> provider_paths.xml
Storing the image in this path
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths >
<files-path name="my_images" path="images/"/>
<files-path name="my_docs" path="docs/"/>
</paths>
Path defined like this to store image DCIM.
public String getEmpThumbImageDirPath() {
try {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_
DCIM).toString() + EmpConstants.appDir;
}catch (Exception e) {
Log.d("eEmp/ImgDir", e.toString());
return "";
}
camera is opening and capturing the image but image is not loading. What mistake I have done.
Any help would be appreciated.
04-13 20:05:43.738 30272-30272/com.efftronics.android.eEmployee E/ContentValues: createImageFile: directory was created successfully.
04-13 20:05:43.739 30272-30272/com.efftronics.android.eEmployee E/ContentValues: run: image folder path is: /storage/emulated/0/FolderName/InsideFolderNameIFYOUWant
04-13 20:05:43.739 30272-30272/com.efftronics.android.eEmployee E/ContentValues: createImageFile: image file name is: imageName_1523630143739
04-13 20:05:49.791 30272-30272/com.efftronics.android.eEmployee E/ContentValues: createImageFile: directory already exists.
04-13 20:05:49.792 30272-30272/com.efftronics.android.eEmployee E/ContentValues: run: image folder path is: /storage/emulated/0/FolderName/InsideFolderNameIFYOUWant
04-13 20:05:49.792 30272-30272/com.efftronics.android.eEmployee E/ContentValues: createImageFile: image file name is: imageName_1523630149792
Try this, It works for me:
private void openCamera()
{
try
{
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null)
{
try
{
// Create the File where the photo should go
File photoFile = createImageFile();
// 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, CAPTURE_IMAGE_REQUEST_CODE);
}
else
{
Log.e(TAG, "openCamera: image was not captured.");
}
}
catch (Exception ex)
{
// Eor occurred while creating the File
ex.printStackTrace();
}
}
}
catch (Exception e)
{
Log.e(TAG, "openCamera: exception while opening camera:");
e.printStackTrace();
}
}
private String mCurrentPhotoPath;
private File createImageFile()
{
// Create an image file name
File sd = Environment.getExternalStorageDirectory();
File imageFolder = new File(sd.getAbsolutePath() + File.separator +
"FolderName" + File.separator + "InsideFolderNameIFYOUWant");
if (!imageFolder.exists())
{
if (imageFolder.mkdirs())
{
Log.e(TAG, "createImageFile: directory was created successfully.");
}
else
{
Log.e(TAG, "createImageFile: directory was not created.");
}
}
else
{
Log.e(TAG, "createImageFile: directory already exists.");
}
Log.e(TAG, "run: image folder path is: " + imageFolder.getAbsolutePath());
File image = null;
File mediaFile = new File(imageFolder + File.separator );
String imageFileName = "imageName_" + System.currentTimeMillis();
Log.e(TAG, "createImageFile: image file name is: " + imageFileName);
try
{
image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
mediaFile /* directory */);
}
catch (IOException e)
{
Log.e(TAG, "createImageFile: exception occurred while creating image file:\n");
e.printStackTrace();
}
if (image != null)
{
// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
else
{
Log.e(TAG, "createImageFile: image was not created.");
return null;
}
}
Add this in your manifest file:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
Then create and xml resource directory and add and file_paths xml file and inside that add this:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="PathOfYourFolder" />
</paths>
And in your on activity result method just use the mCurrentPhotoPath to load the image into your image view. Use Picasso to do that.
I am in trouble with this issue, couldn't find any way out yet.
I have configured a Fileprovider in manifest.
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.package.name.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
where the #xml/file_paths is like
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="my_images"
path="Android/data/com.package.name/files/Pictures" />
<external-files-path
name="my_images_" />
</paths>
And my java code is like below
private void selectImageFromCamera() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getActivity().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
Log.e("selectImageFromCamera", "Error: " + ex);
}
// Continue only if the File was successfully created
if (photoFile != null) {
try {
Uri photoURI = FileProvider.getUriForFile(getActivity(),
"com.package.name.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, CAMERA_PIC_REQUEST);
} catch (Exception e) {
e.printStackTrace();
Log.e("selectImageFromCamera", "Error: " + e);
}
}
}
The method createImageFile()
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
Log.e("storageDir", "storageDir: " + storageDir.getAbsolutePath());
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
myCurrentPhotoPath = image.getAbsolutePath();
return image;
}
But I am always getting problem when creating photoUri, to be exactly at below line.
Uri photoURI = FileProvider.getUriForFile(getActivity(),
"com.package.name.fileprovider",
photoFile);
Error in try catch
.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/com.package.name/files/Pictures/JPEG_20170417_115925_834197983.jpg
Any help would be appreciated. Thanks.
I am trying to make an app that takes a photo and displays it using emulator instead of a device. I followed the steps from this android doc:
Part 1) Here is my code where it gets stucks : file = FileProvider.getUriForFile(this, "edu.android.notetakingapplication.provider", createFileDir());
This part is in the Mainxml
if (!mediaStorageDir.exists()){
if (!mediaStorageDir.mkdirs()){
Log.d("NoteTaking", "failed to create directory");
return null;
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
return new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
}
Here is my app manifest file's provider section
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="edu.android.notetakingapplication.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
and provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="Android/data/edu.android.notetakingapplication/files/Pictures"/>
</paths>
Part 2) If i keep provider_paths as this
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
I am able to open camera app, take photo and view it on the same page, but, here is the catch, I am storing the path into database and trying to retrieve all the images on a page. Here is when it gives an error:
Unable to decode stream: java.io.FileNotFoundException: /external_files/Pictures/NoteTaking/IMG_20170226_230608.jpg (No such file or directory)
Try this surely it will work:
Uri mImageCaptureUri;
private void operCamera() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
mImageCaptureUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
"tmp_avatar_" + String.valueOf(System.currentTimeMillis()) + ".jpg"));
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, mImageCaptureUri);
intent.putExtra("return-data", true);
startActivityForResult(intent, 1);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1 && mImageCaptureUri != null && resultCode != 0) {
if (mImageCaptureUri != null) {
String path1 = mImageCaptureUri.getPath();
if (path1 != null) {
File file1 = new File(path1);
Uri capturedUri = Uri.fromFile(file1);//here you get the URI
//you can easily get the path from URI if you need
}
}
}
}
Unable to decode stream: java.io.FileNotFoundException: /external_files/Pictures/NoteTaking/IMG_20170226_230608.jpg (No such file or directory). Of course you get that error as it is a non valid path for File object or a file stream. You are saving the uri in a wrong way to the database. You better save File:getAbsoluthePath() instead of Uri:getPath().