Saving Camera Photo To Gallery From Fragment using File Provider - java

I am following along with Googles Official docs on how to save a picture take with the camera to the gallery.
They want you to create a file using getExternalFilesDir.
String mCurrentPhotoPath;
private File createImageFile() throws IOException {
// Create an image file name
String imageFileName = "JPEG_" + UUID.randomUUID();
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
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
mCurrentPhotoPath is equal to /storage/emulated/0/Android/data/com.mycompany.myapp/files/Pictures/JPEG_22fda6f2-dad9-4dd9-b327-c1130c8df0eb187766077.jpg
But in the very next section, the most important section, Add the Photo to a Gallery,
They say:
If you saved your photo to the directory provided by
getExternalFilesDir(), the media scanner cannot access the files
because they are private to your app.
Which is the exact method getExternalFilesDir() they used. :-(
So I looked at the documentation on that too. And I don't understand well enough yet to figure out which directory method I need to use. I tried getFilesDir() but it doesn't like the Environment.DIRECTORY_PICTURES.
But they do not offer a way to save to the gallery using their method. Their code snippet doesn't work
private void cameraIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
}
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(getActivity().getApplicationContext(), "com.mycompany.myapp.fileprovider", photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_CODE_CAPUTURE_IMAGE);
}
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_CAPUTURE_IMAGE && resultCode == Activity.RESULT_OK) {
galleryAddPic();
}
}
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
So my app does NOT save a picture to the gallery. I don't see where it saves it at all.
Anyone know what I did wrong?

When you add files to Android’s filesystem these files are not picked up by the MedaScanner automatically, also Android runs a full media scan only on reboot. The problem is that a full scan is taking long time.
One solution is using the static scanFile() method. If you simply need to know when the files have been added, you could use MediaScannerConnection’s static method scanFile() together with a MediaScannerConnection.OnScanCompletedListener. The static method scanFile() is badly named, as it actually takes an array of paths and thus can be used to add multiple files at once and not just one, but it nevertheless does what we want.
Here’s how to use this method:
MediaScannerConnection.scanFile(
getApplicationContext(),
new String[]{file.getAbsolutePath()},
null,
new OnScanCompletedListener() {
#Override
public void onScanCompleted(String path, Uri uri) {
Log.v("grokkingandroid",
"file " + path + " was scanned seccessfully: " + uri);
}
});
the below information is the parameters for the static scanFile() method.
context : The application context
paths : A String array containing the paths of the files you want to add
mimeTypes : A String array containing the mime types of the files
callback : A MediaScannerConnection.OnScanCompletedListener to be notified when the scan is completed
The OnScanCompletedListener itself must implement the onScanCompleted() method. This method gets the filename and the URI for the MediaStore.Files provider passed in as parameters.
I hope this will help.

Related

File does not exist exception although file does exist

I need to user to choose a file from storage. Once user chooses the file I can't do anything with it because it comes back with an exception "File does not exist", although it most certainly does. Permissions are granted and I even take persistent URI permission.
This sample code is not the prettiest, I know but it still shouldn't give me that excpetion I think. Did I write something wrong or that could cause this problem?
Currently this sample code doesn't throw an exception but if statement fails and logs "File does not exist". I need it to pass if statement and call openMap(). If I were to remove the if statement I would get org.mapsforge.map.reader.header.MapFileException: file does not exist: content:/com.android.externalstorage.documents/document/primary%3Aestonia.map
final ActivityResultLauncher<Intent> sARL = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK){
Intent data = result.getData();
assert data != null;
Uri uri = data.getData();
File oFile = new File(uri.getPath());
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (new File(uri.getPath()).exists()){
openMap(oFile);
}else{
Log.w("File", "File does not exist");
}
}
}
});
public void openFileDialog(){
Intent data = new Intent(Intent.ACTION_OPEN_DOCUMENT);
data.setType("*/*");
data = Intent.createChooser(data, "Choose tile");
sARL.launch(data);
}
Try below code
File oFile = new File(data.getData().getPath());
if (oFile.exists()){
Toast.makeText(this, "exist: "+oFile.exists(), Toast.LENGTH_SHORT).show(); //returns true
openMap(oFile);
}else{
Log.w("File", "File does not exist");
}
Let me know if the problem not solved yet.
The problem was with the Uri. If I hard code path to the file then everything is fine, but with file choosing intent I can't actually access the real file path, as far as I know. File contents can still be read but I needed the path.
File oFile = new File(Environment.getExternalStorageDirectory() + filePath)
This did the trick and I got the actual file loaded.

Getting error when trying to write on a file open failed: EACCES (Permission denied) [duplicate]

I'm working on an Android application where one of the features is to let the user choose a file to open (I'm wanting to open a plain text .txt file). I've worked on Android apps before with Java, but for this one, I'm using Kotlin, and it's my first time using Kotlin.
I currently have the app display a file chooser and let the user tap the file they want to open. Then I'm trying to use a File object to open the file and do a forEachLine loop. But for some reason, it's throwing a java.io.FileNotFoundException (No such file or directory) with the file chosen from the file picker. I'm not sure what's wrong, and if I have to do some conversion to convert the file path?
The code for my 'load' button:
val btn_load: Button = findViewById<Button>(R.id.btn_load_puzzle)
btn_load.setOnClickListener {
val intent = Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT)
startActivityForResult(Intent.createChooser(intent, "Select a file"), 111)
}
My function to respond to the file selection:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Selected a file to load
if ((requestCode == 111) && (resultCode == RESULT_OK)) {
val selectedFilename = data?.data //The uri with the location of the file
if (selectedFilename != null) {
val filenameURIStr = selectedFilename.toString()
if (filenameURIStr.endsWith(".txt", true)) {
val msg = "Chosen file: " + filenameURIStr
val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT)
toast.show()
File(selectedFilename.getPath()).forEachLine {
val toast = Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT)
toast.show()
}
}
else {
val msg = "The chosen file is not a .txt file!"
val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG)
toast.show()
}
}
else {
val msg = "Null filename data received!"
val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG)
toast.show()
}
}
}
The FileNotFound exception is thrown on the line where it creates the File object to do the forEachLine loop:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=111, result=-1, data=Intent { dat=content://com.android.externalstorage.documents/document/0000-0000:Sudoku puzzles/hard001.txt flg=0x1 }} to activity {com.example.sudokusolver/com.example.sudokusolver.MainActivity}: java.io.FileNotFoundException: /document/0000-0000:Sudoku puzzles/hard001.txt (No such file or directory)
You did not receive a file path, you received a Uri. You have to use Uri based APIs such as ContentResolver.openInputStream() to access the contents at that Uri as Android does not grant your app direct File access to the underlying file (it could also be streamed from Google Drive or downloaded directly from the internet without your app being aware that this is happening):
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Selected a file to load
if ((requestCode == 111) && (resultCode == RESULT_OK)) {
val selectedFilename = data?.data //The uri with the location of the file
if (selectedFilename != null) {
contentResolver.openInputStream(selectedFilename)?.bufferedReader()?.forEachLine {
val toast = Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT)
toast.show()
}
} else {
val msg = "Null filename data received!"
val toast = Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG)
toast.show()
}
}
}
Here we can assume we get contents of the proper format by passing in the proper mime type to our request (as there is no requirement that a text file end in exactly the .txt extension as part of its path):
val intent = Intent()
.setType("text/*")
.setAction(Intent.ACTION_GET_CONTENT)
startActivityForResult(Intent.createChooser(intent, "Select a file"), 111)
Which will automatically make any non text file unable to be selected.
If you are getting "msf:xxx" in URI, use below solution where I have created temp file in app cache directory and deleted same file after completion of my task:
if (id != null && id.startsWith("msf:")) {
final File file = new File(mContext.getCacheDir(), Constant.TEMP_FILE + Objects.requireNonNull(mContext.getContentResolver().getType(imageUri)).split("/")[1]);
try (final InputStream inputStream = mContext.getContentResolver().openInputStream(imageUri); OutputStream output = new FileOutputStream(file)) {
final byte[] buffer = new byte[4 * 1024]; // or other buffer size
int read;
while ((read = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
output.flush();
return file;
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
I have fixed this issue and it's working 100% for msf. :)
Also Delete the temp file after the completion of your work:
private void deleteTempFile() {
final File[] files = requireContext().getCacheDir().listFiles();
if (files != null) {
for (final File file : files) {
if (file.getName().contains(Constant.TEMP_FILE)) {
file.delete();
}
}
}
}
Here TEMP_FILE value is "temp."
You cannot open a Java File on a ÙRI converted to a String, the "path" section of the URI has no relation to a physical file location.
Use a contentResolver to get a Java FileDescriptor to open the file with.
val parcelFileDescriptor: ParcelFileDescriptor =
contentResolver.openFileDescriptor(uri, "r")
val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
This method is compatible with Android 10 where file paths for non App private directories are not usuable.
https://developer.android.com/training/data-storage/shared/documents-files
Open a Bitmap file given its URI:
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
return image;
}
For msf: file uri format, present from Andorid 10.
you can check this solution: https://stackoverflow.com/a/68827976/15342371
This fetch path without copying the file.

Android, Java, Create directory on SD card programmatically using Storage Access Framework (SAF)

My problem is that I do not manage to create a directory on a SD card that is plugged in an Android portable device.
Below is the Java code I am trying to get to work: I am trying to create the directory sable under /storage/BF4F-1107/:
public class AnActivity extends AppCompatActivity
{
private static final int N_CREATE_DIRECTORY = 1;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// SD card
String s_sdCardStorage = "/storage/BF4F-1107/";
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("file/*");
intent.putExtra(Intent.EXTRA_TITLE, s_destFilePath);
startActivityForResult(intent, N_CREATE_DIRECTORY);
// HERE
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(requestCode == N_CREATE_DIRECTORY)
{
if(data != null)
{
Uri uri = data.getData();
DocumentFile docFileSDCardStorage = DocumentFile.fromSingleUri(this, uri);
try
{
DocumentFile docFileDir = docFileSDCardStorage.createDirectory("sable");
}
catch(UnsupportedOperationException exn)
{
System.out.println(exn.getMessage());
}
}
}
}
}
What happens is:
the code in onCreate() is executed.
Then the program blocks at // HERE.
On the portable device, appears a "dialog" which shows /storage/BF4F-1107/ and a button Save which I "press".
Once "pressed", the code in onActivityResult() is executed.
But the result is that the directory sable under /storage/BF4F-1107/ is not created.
And the execution path goes through the catch clause, the exception UnsupportedOperationException is raised and null is printed at System.out.println(exn.getMessage());.
Edit 2: An empty file _storage_BF4F-1107_ is created under /storage/BF4F-1107/.
Can you help me make this code work?
Additionally, I would like the directory sable to be created silently.
I do not want to user to have to touch "Save".
I am trying to use the Storage Access Framework (https://developer.android.com/training/data-storage/shared/documents-files) because the mkdirs method of the java.io.File class doesn't work (I get permission denied exceptions) when I try to create a directory on the SD card.
Edit: my Android version is 6.0.1
Thank you.
Use ACTION_OPEN_DOCUMENT_TREE to let the user choose de SD card.
After that you can create as many files and directorys in the choosen directory.
If you only want to create one file with SAF use ACTION_CREATE_DOCUMENT where the user chooses the location and file name.

Delete a file just after it is shared in android

I'm sharing image file to WhatsApp.
Whatsapp can't read private file so I saved it into public directory.
I want --> when a user shares the image it should be deleted from the storage.
try {
String uniqueName = System.currentTimeMillis() + "";
File tempFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "ABBM-temp");
if (!tempFile.exists()) {
tempFile.mkdirs();
}
tempFile = new File(tempFile + uniqueName + ".png");
FileOutputStream fOut = new FileOutputStream(tempFile);
b.compress(Bitmap.CompressFormat.PNG, 100, fOut);
fOut.flush();
fOut.close();
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
intent.setType("image/png");
startActivity(Intent.createChooser(intent, "Share image via"));
//tempFile.delete();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "Image not shared, please grant storage permission", Toast.LENGTH_SHORT).show();
}
The above code opens sharing intent but deletes file in background and I'm unable to share the file.
Share file and delete it.
Thank you..
Your code is a bit clunky and is guaranteed to have errors in the latest versions of Android. Here are some observations:
Solution 1
1. Use a File Provider and write to private storage area
I know that you are writing to the main storage, so other apps can read the file, but it shouldn't be done that way. Use a file provider and get a Uri from that. Here is the documentation: File Provider Documentation. When you have your file provider set-up(believe me it's easy, you just have to add a snippet to your manifest and add a xml path) you can write privately to the memory. Like this:
FileOutputStream fOut = openFileOutput("the_name_of_file_here", Context.MODE_PRIVATE);
b.compress(Bitmap.CompressFormat.PNG, 100, fOut);
fOut.flush();
fOut.close();
Then using a getUriForFile you can get a Uri usable by any app that you give access to. Like this:
File file = new File(getFilesDir(), "the_name_of_file_here");
Uri uri = getUriForFile(this, "com.yourpackagename.fileprovider", file);
2. Use ShareCompat
When you have set-up the FileProvider, I suggest to you that for sharing files use ShareCompat instead of a regular sharing activity. ShareCompat has the advantage of being officially supported by Google and you can also share a multitude of files with it. Without having to grant URI_PERMISSION to the other app. Here's an example:
Intent shareIntent = ShareCompat.IntentBuilder.from(this)
.setType(type) //This is the MIME type
.setStream(uri) //This is the uri we got earlier
.getIntent();
shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(shareIntent); //Starting the activity
I advise you to take a look here at this blog: Getting Started with Share-Compat for more information. Hope this helps.
Solution 2
Traditional Solution:
If you still want to use your function, because of preference or because it suits your app better, here is one thing you can do:
Instead of using startActivity(Intent) use startActivityForResult(Intent, int). With that what you can do is pass an integer value which will work as an id. Then when the file is shared and you return back to your app, the onActivityResult() function will be fired. It will have a requestCode. Like this:
//Create your share intent as you do in your code, then:
startActivityForResult(intent, 512); //I passed 512 you can pass anything you want
Then override this function:
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//Check if it is from the same code, if yes delete the temp file
if(requestCode == 512) {
tempFile.delete();
}
}

When i start the intent ACTION_OPEN_DOCUMENT_TREE it automatically opens empty recent folder?

What i'm trying to achieve is to delete a file on the sd card, i tried the file.delete method which didn't work because sd cards are read only now.
So i read a post about SAF (Storage Access Framework) to gain sd card write access by storing the treeUri we get in onActivityResult.
File deleting works fine now, but when i start the intent Intent.ACTION_OPEN_DOCUMENT_TREE sometimes it returns the recent folder which is empty and the way to show the files on the sdcard is to click on the overflow icon and then select show SDCARD or Internal Storage which may confuse some people when they run my app.
i tried adding these to my intent: intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
intent.putExtra("android.content.extra.FANCY", true);
intent.putExtra("android.content.extra.SHOW_FILESIZE", true);
which works on some devices, but it's a private API and on some it doesn't work.
So is there a way to like automatically open a specific directory or show a hint dialog with steps explaining which directory they should chose?
private void getSDCardAccess(){
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath());
startActivityForResult(intent, REQUEST_EXTERNAL_ACCESS);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_EXTERNAL_ACCESS && resultCode == RESULT_OK) {
Uri treeUri = null;
if (data != null){
treeUri = data.getData();
}
if (treeUri != null && getActivity() != null) {
getActivity().getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
StorageUtil storageUtil = new StorageUtil(getActivity());
//Takes the access so that we can use it again after the app reopens
storageUtil.storeTreeUri(treeUri.toString());
Log.d(TAG, "treeUri: " + treeUri.toString());
}else{
Log.d(TAG,"uri is empty!");
}
}
}
is there a way to like automatically open a specific directory
If you have a Uri to it that you got from ACTION_OPEN_DOCUMENT_TREE previously, you should be able to supply that via DocumentsContract.EXTRA_INITIAL_URI. Per the ACTION_OPEN_DOCUMENT_TREE documentation:
Callers can set a document URI through DocumentsContract.EXTRA_INITIAL_URI to indicate the initial location of documents navigator. System will do its best to launch the navigator in the specified document if it's a folder, or the folder that contains the specified document if not.
or show a hint dialog with steps explaining which directory they should chose?
You would need to do that yourself, prior to calling startActivityForResult() for the ACTION_OPEN_DOCUMENT_TREE request.

Categories

Resources