Delete a file just after it is shared in android - java

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();
}
}

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.

copy audio file working on emulator but not working on android device

my code below worked with emulator (android 11 & api 30).
private void copy(File in, File out) throws IOException {
FileInputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream(in);
os = new FileOutputStream(out);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
Log.d("AAA", String.valueOf(out.length()));
}
} finally {
assert is != null;
is.close();
assert os != null;
os.close();
}
}
where i put this method
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK) {
String path = RealPathUtil.getRealPath(SetVoiceIntroActivity.this, data.getData());
File in = new File(path);
File out = new File(getFilesDir(), "record.mp3");
try {
copy(in, out);
Log.d(TAG, out.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
binding.voicePlayerView.setVisibility(View.VISIBLE);
binding.buttonDeleteVoice.setVisibility(View.VISIBLE);
binding.voicePlayerView.setAudio(out.getAbsolutePath());
}
}
but when i try with my phone it have a problem. how can i fix it?
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.yolo.hopel, PID: 19152
java.lang.AssertionError
at com.yolo.hopel.Activities.SetProfile.SetVoiceIntroActivity.copy(SetVoiceIntroActivity.java:251)
at com.yolo.hopel.Activities.SetProfile.SetVoiceIntroActivity.onActivityResult(SetVoiceIntroActivity.java:64)
at android.app.Activity.dispatchActivityResult(Activity.java:8292)
at android.app.ActivityThread.deliverResults(ActivityThread.java:5136)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:5184)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2175)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7860)
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:1075)
i catch it and i get the message
2021-08-03 19:29:10.310 25013-25013/com.yolo.hopel D/error: /storage/emulated/0/NCT/NgayKhacLa-DenDJGiangPhamTripleD-5393909.mp3: open failed: EACCES (Permission denied)
but i checked permission before
binding.buttonSetVoiceGallery.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkPermission();
}
});
private void checkPermission() {
PermissionListener permissionlistener = new PermissionListener() {
#Override
public void onPermissionGranted() {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.setType("audio/*");
startActivityForResult(intent, 0);
}
#Override
public void onPermissionDenied(List<String> deniedPermissions) {
Toast.makeText(SetVoiceIntroActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
}
};
TedPermission.with(this)
.setPermissionListener(permissionlistener)
.setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
.setPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.check();
}
i'm sorry but my post is mostle code, i must add some text...
please help me, thanks everyone and have a nice day!
An AssertionError is thrown when the programmer thinks that something has happened which he/she didn't expect it to happen. So mostly this seems like a ScopedStorage issue to me since you explicitly mentioned the case with Android 11. You can try reading about the scoped storage in Android . Also the RealPathUtil that you used for github doesn't work for the scoped storage case
You can refer to this answer for some commonly needed operations for Files using Scoped Storage
Some other points you may need to know about Scoped Storage
The WRITE_EXTERNAL_STORAGE permission is deprecated and it is replaced by the MANAGE_EXTERNAL_STORAGE which is allowed on Play Store after manual approval by Google. You can now access all the publically available files in Android after Q without need the WRITE_EXTERNAL_PERMISSION. So you must not request the MANAGE_EXTERNAL_STORAGE permission unless you're building a File Explorer or anything like that
You can write to these public directories using the MediaStore class if you need explicit access to external storage, if not then you can use context.getFilesDir() which gives you app private access to the external storage under the /data/data/yourpackagename directory. You must manage this space explicitly
If you need temporary file storage, you should prefer using context.getCacheDir() and the space here will be managed by Android itself
Under no circumstances, you can get access to the app specific directory, even after you have MANAGE_EXTERNAL_STORAGE
You got a nice uri and then you mess around with all kind of untrusty code to obtain a file path to use FileInputStream for it.
Completely unnecessary.
Open an inputstream for the obtained uri and use it as it was your fileinputstream.
InputStream is = getContentResolver.openInputStream(data.getData());

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.

Saving Camera Photo To Gallery From Fragment using File Provider

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.

Android ActivityNotFoundException failing to catch on Intent in OS 4.1.2 (works on 4.0.3)?

Thanks for taking a look. I am a very old programmer but absolute newbie to Android/Java. Although I haven't found a way to make code colorful and pretty here yet ;).
I am starting an activity to open a file with a bogus/fake extension (mime type) so I can force this ActivityNotFoundException exception (to test).
Somehow, this exception never happens on my Samsung Galaxy S3 (OS 4.1.2). Adobe Reader opens up and errors that the file is not a PDF (it's not). However on an older ASUS Transformer tablet running 4.0.3 the exception is caught properly.
The code in question is like so:
// this file exists (check omitted)..
File docFile = new File("/path/to/file.bogusextension");
// intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
// getting doc Uri
Uri fileUri = Uri.fromFile(docFile);
// getting doc mimeType
MimeTypeMap mime = MimeTypeMap.getSingleton();
String extension = MimeTypeMap.getFileExtensionFromUrl(fileUrl);
String mimeType = mime.getMimeTypeFromExtension(extension);
intent.setDataAndType(fileUri, mimeType);
// this is for an adobe native extension, just getting FREContext
// in the class below and mapping functions
DocLauncherExtensionContext extensionContext = (DocLauncherExtensionContext) context;
Activity activity = extensionContext.getActivity();
try
{
activity.startActivity(intent);
}
catch (ActivityNotFoundException e)
{
Toast.makeText(context.getActivity(), "This never happens!", Toast.LENGTH_LONG).show();
}
Is there anything blatent wrong with this code that would allow a file with any extension what-so-ever to be constantly thrown at Adobe Reader? I have cleared my defaults (Settings->Application Manager->Reset). Everything I startActivity is just hurled at Adobe Reader.
Any thoughts appreciated! Thank you!
Intent intent = new Intent();
needs to become:
Intent intent = new Intent(Intent.ACTION_VIEW);
You were putting data and an ActionCode into the intent, however you never told it what it was an intent for.

Categories

Resources