How to handle application created images? - android

So, I would like to upload files to server, and my application can run without network, and because of this, I have to save the files, that the user picks and upload later.
I don't want to save thease pictures into database, bacause as I read it is a bad practice, so I decided to I create a copy of the file, and after that I save the copied file uri string to database.
It is working good, but if I close the application, and opened it again, the uri is not valid, or probably I don't have permission anymore to read or write.
So how can I perform this behavior?
And why lose the application the uri permission, if it is created by himself?
I tired to get persistable uri permission, but I get an java.lang.SecurityException: No persistable permission grants found exception.
File copy
val copyOfFile = createFile(
application.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
fileMetaData.extension,
fileMetaData.fileName,
true
)
val outPut = FileOutputStream(copyOfFile)
inputStream.copyTo(outPut)
val newFileUri =
FileProvider.getUriForFile(application, application.packageName, copyOfFile)
application.contentResolver.takePersistableUriPermission(
newFileUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
return CopyFileResult(
copyOfFile.name,
fileMetaData.extension,
newFileUri.toString()
)
Open uri
val uri = Uri.parse(document.uriString!!)
val bitmap = documentManagerService.getBitmapFromUri(uri)

I found my mistake.
I save the filePath to database instead of uri string, and later the file is exists and I can use.

Related

Android list all files in drectory from SCHEME_CONTENT uri

I want the get a list of files in same directory of a given single file from uri e.g [picture #1.1]
content://org.owncloud.documents/document/79
and then select file [picture #1.2]
when I select requested fields
val filePath = arrayOf(MediaStore.Images.Media.DATA, DISPLAY_NAME, RELATIVE_PATH)
val c: Cursor = context.contentResolver.query(uri!!, filePath, null, null, null)!!
c.moveToFirst()
I receive [picture #1.3]
within this directory e.g content://org.owncloud.documents/document/79
Solution 1 :
Query it, but something like https://stackoverflow.com/a/65070765/1079990 doesn't work.
I guess I've grab directory from it and then query all files.
This is the main question !
Solution 2 :
Select the directory directly with eg.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, 123)
But here I can't access ownCloud, how to be able to select this as well ?
First if ACTION_OPEN_DOCUMENT_TREE cannot give you the tree then what you want will be impossible.
Second if you used ACTION_OPEN_DOCUMENT you got an uri for a file. And permission to read and or write it.
Even if you could manage to build up an uri for the directory the file was in (quite possible if you choosed a file from device storage) you would not have permission to read it.

How to pick file from external storage using file picker in Android

I am having a problem with selecting image file from external storage using file picker in Android. This question is the consequence of this question - No such file or diectory error in image file upload using Retrofit in Android. What my problem is opening and reading file from external storage on activity result. I want to convert result URI into File.
I read a pdf file from download folder on activity result
Uri bookUri = data.getData();
if(bookUri!=null)
{
String filePath = bookUri.toString();//bookUri.toString()
String mime = app.getMimeType(filePath);
if(mime!=null && !mime.isEmpty() && (mime.toLowerCase()=="application/pdf" || mime.toLowerCase()=="application/txt" || mime.toLowerCase()=="application/text"))
{
bookFile = new File(bookUri.getPath());
ivBookFile.setImageResource(R.drawable.book_selected);
}
else{
Toast.makeText(getBaseContext(),"Unable to process file you have chosen.",Toast.LENGTH_SHORT).show();
}
}
As you can see I used new File(bookUri.getPath()); to convert into File. The above code works well. It is working. The problem is now I am trying to open an image file in DCIM/Camera folder on activity result.
This is the code I used
Uri selectedImageUri = data.getData();
if(selectedImageUri!=null)
{
try{
bmpCoverImage = MediaStore.Images.Media.getBitmap(getContentResolver(), selectedImageUri);
imageFile = new File(selectedImageUri.getPath());
if(bmpCoverImage!=null)
{
ivCoverImage.setImageBitmap(bmpCoverImage);
}
}
catch (IOException e)
{
Toast.makeText(getBaseContext(),"An error occurred with the file selected",Toast.LENGTH_SHORT).show();
}
}
As you can see I used new File(selectedImageUri.getPath()); like I did in reading pdf file. This time the code is not working. When I do operation with the file like in previous question, it gives me error.
I used this way also
imageFile = new File(Environment.getExternalStorageDirectory(),selectedImageUri.getPath());
I got the same error. How can I open the image file correctly from external storage? How can I convert the chosen file URI from external storage into File?
I am having a problem with selecting image file from external storage using file picker in Android
If you are referring to the code that you are using in this question, you are not "using file picker". You are using ACTION_GET_CONTENT, which has never been a "file picker", nor will it ever be a "file picker".
I want to convert result URI into File.
Usually, that is not necessary. But, if that is what you want to do:
use ContentResolver and openInputStream() to get an InputStream on the content represented by the Uri
create a FileOutputStream on your desired file
use Java I/O to copy the bytes from the InputStream into the FileOutputStream
The above code works well. It is working.
It works for the small number of devices that you tested, for the specific activities that the user chose to handle the ACTION_GET_CONTENT request. It will not work on most Android devices, and it will not work in most circumstances. The only time that code will work is if the Uri has a file scheme. Most of the time, it will not. Instead, it will have a content scheme, representing content supplied by a ContentProvider.
Please how can I open the image file correctly from external storage?
If you wish to continue using ACTION_GET_CONTENT, please understand that this has nothing to do with external storage specifically. You are not getting a file, on external storage or elsewhere. You are getting a Uri. This is akin to a URL, such as the URL for this Web page. Just as a URL does not necessarily point to a file on your hard drive, a Uri does not necessarily point to a file on the filesystem. Use a ContentResolver and DocumentFile to work with the Uri and the content that it identifies.
If you want to always get files on external storage (and nowhere else), then use an actual file picker library.

java.io.IOException: Cannot make changes to file

I am using JAudioTagger library for reading and writing tags for an audio file. I am able to read the tags but unable to write them.
I am retrieving audio file path like this :
private String getSongPath(long songId) {
String path = null;
ContentResolver contentResolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection = {MediaStore.Audio.Media.DATA};
String selection = MediaStore.Audio.Media._ID + " == ?";
String[] selectionArgs = {String.valueOf(songId)};
Cursor cursor = contentResolver.query(uri, projection, selection, selectionArgs, null);
if (cursor != null) {
int pathCol = cursor.getColumnIndexOrThrow(projection[0]);
cursor.moveToFirst();
path = cursor.getString(pathCol);
cursor.close();
}
return path;
}
Then to write tags using JAudioTagger :
File songFile = new File(path); // path looks like /storage/3932-3434/Music/xyz.mp3
AudioFile audiofile = = AudioFileIO.read(songFile);
Tag tag = = audiofile.getTag();
tag.setField(FieldKey.TITLE, title);
// some more setField calls for different feilds
audiofile.commit();
The commit() method is giving following Exception :
org.jaudiotagger.audio.exceptions.CannotWriteException:
java.io.IOException: Cannot make changes to file xyz.mp3 at
org.jaudiotagger.audio.mp3.MP3File.commit(MP3File.java:799) at
com.techapps.musicplayerplus.MainActivity$17.onClick(MainActivity.java:2125)
at
android.support.v7.app.AlertController$ButtonHandler.handleMessage(AlertController.java:157)
at android.os.Handler.dispatchMessage(Handler.java:102) at
android.os.Looper.loop(Looper.java:148) at
android.app.ActivityThread.main(ActivityThread.java:5417) 06-18
10:59:48.134 8802-8802/com.techapps.musicplayerplus W/System.err:
at java.lang.reflect.Method.invoke(Native Method) at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) Caused
by: java.io.IOException: Cannot make changes to file Saibo.mp3 at
org.jaudiotagger.audio.mp3.MP3File.precheck(MP3File.java:824) at
org.jaudiotagger.audio.mp3.MP3File.save(MP3File.java:850) at
org.jaudiotagger.audio.mp3.MP3File.save(MP3File.java:783) at
org.jaudiotagger.audio.mp3.MP3File.commit(MP3File.java:795)
I am running this code on Android 6 while my app is targeted at SDK 22. I have also mentioned following permission in manifest.
android.permission.WRITE_EXTERNAL_STORAGE
Still I am unable to write to SD card. Please help me. Thanks in advance.
You have to use Storage Access Framework (SAF) to access SD Card from API 19 (Kitkat) onward.
First we need to ask user to provide a URI of the folder we want to access. If we want access to entire SD card, user needs to provide URI of SD card's root folder.
For example, when user hits Edit button, we have to first show hint dialog box, asking user to select required directory in SD Card which we want to access. You can display following image in hint dialog box to ask user to select root directory of SD Card :
When user dismisses hint dialog box, you need to trigger Storage Access Framework :
private void triggerStorageAccessFramework() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCESS);
}
public final void onActivityResult(final int requestCode, final int resultCode, final Intent resultData) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_STORAGE_ACCESS) {
Uri treeUri = null;
// Get Uri from Storage Access Framework.
treeUri = resultData.getData();
pickedDir= DocumentFile.fromTreeUri(this, treeUri);
if (!isSDCardRootDirectoryUri(treeUri)) {
Toast.makeText(this, "Wrong directory selected. Please select SD Card root directory.", Toast.LENGTH_LONG).show();
createSDCardHintDialog().show();
return;
}
// Persist URI in shared preference so that you can use it later.
SharedPreferences sharedPreferences = getSharedPreferences(App.PREFERENCE_FILENAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(App.SDCARD_URI_KEY, treeUri.toString());
editor.apply();
// Persist access permissions, so you dont have to ask again
final int takeFlags = resultData.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}
private boolean isSDCardRootDirectoryUri(Uri treeUri) {
String uriString = treeUri.toString();
return uriString.endsWith("%3A");
}
Once you get Uri of user picked directory, you can perform write operation using SAF : (creadit : this answer )
public void writeFile(DocumentFile pickedDir) {
try {
DocumentFile file = pickedDir.createFile("image/jpeg", "try2.jpg");
OutputStream out = getContentResolver().openOutputStream(file.getUri());
try {
// write the image content
} finally {
out.close();
}
} catch (IOException e) {
throw new RuntimeException("Something went wrong : " + e.getMessage(), e);
}
}
It could be that you pointing to non existing file.
Check your path file by using Log.
Log.d("Activity", "path = " + path);
Android-M or API 23 introduced Runtime Permissions for reducing security flaws in android device.
To update your apps using Google Play services to handle Android 6.0 permissions, it’s good practice to manage the user’s expectations in setting permissions that the runtime may require. The following link will help you avoid potential issues.
https://developer.android.com/training/permissions/requesting.html
have you declared the permission
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ?
I saw that you already created an issue in the JAudioTagger GitHub repository which was advisable, but never got a universally working solution. My findings so far:
The answer mentioning SAF is correct, but it won't help you as SAF will provide a DocumentFile, not a File.
You might try to modify JAudioTagger to your needs, replacing File with DocumentFile, but the latter one has not all functions you will need.
Also InputStream and OutputStream will not help you, as JAudioTagger needs File and internally heavily uses RandomAccessFile which is not available either.
Google "forgot" to provide some getRandomAccessFileFromUri() which makes things even worse (Yes, there are hacks using Java reflection to work around this limitation...).
The "/proc/self/fd" method (How to handle SAF when I can only handle File or file-path?) will also not work immediately, as JAudioTagger needs copy and renaming functions that are not applicable to this kind of files. Particularly JAudioTagger will not find a suitable file name extension like ".m4a". Of course you could try to change JAudioTagger accordingly.
You might follow the advice to make a copy of the file to your personal storage, then apply JAudioTagger to it and finally copy it back to SD card, but:
If you want to use JAudioTagger to read from SD card, this will, as announced by Google, fail with Android 10. Starting with that version, you will not even have read access to the SD card via the File interface.
Further, the File interface gives you read access to SD cards with Android 9 and below, but not to other SAF devices, like USB OTG memory or SMB shares etc.
Of course you could also copy each file in order to read its metadata, but this will be awfully slow and is not suitable if you have more than a few files.
So my current advices are:
Try the "/proc/self/fd" method and modify JAudioTagger accordingly.
If the changes are too heavy, use the fd method for reading the tags and the copy method for writing.
BTW: I am currently modifying an older version of JAudioTagger for using both File and DocumentFile transparently, but the changes are tremendous, bear a high risk, need some help classes, and the work is unfinished, yet.
BTSW: The DocumentFile functions are painfully slow, compared to the File functions.

How can I load Images from the Gallery by URIs stored in the db without permission denial

I'm currently creating my own gallery app. I use a custom object which holds all required information for a gallery including urls of the images from the local android gallery.
public class GalleryDTO {
private Long id;
private String name;
private List<Uri> photos = new ArrayList<>();
I store this gallery object in a sql lite db.
I have an activity to view this gallery objects. In this activity I load the gallery I want do display - this works.
But when try to load the images:
GalleryDTO galleryDTO=vrGalleryApp.getGalleryService().loadGalleryById(displayedGalleryId);
if(galleryDTO.getPhotos()!=null&&!galleryDTO.getPhotos().isEmpty()){
for(int i=0;i<galleryDTO.getPhotos().size();i++){
Uri imageUrl = galleryDTO.getPhotos().get(i);
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUrl);
Then the following exception is thrown :
java.lang.SecurityException: Permission Denial: opening provider com.google.android.apps.photos.contentprovider.MediaContentProvider from ProcessRecord{3585e48d 8727:at....vrgallery.vrgallery/u0a137} (pid=8727, uid=10137) that is not exported from uid 10071
even I declared the required rights in the manifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
This exception does not occur for galleries which where just saved before viewing them. But when I close my app (e.g. with the task manager) and reopen it then the exception is thrown for these new galleries too.
What do I am wrong?
I store this gallery object in a sql lite db.
That is not going to work in many cases.
Think of a Uri as being akin to a user-authenticated URL in a Web app. The URL may be useful now, but as soon as the user's session times out, the URL becomes useless. The same thing holds true here: by default, a Uri may only be useful until your process terminates.
Then the following exception is thrown :
That is because your rights to that Uri expired, probably because your process was terminated.
Also, bear in mind that the Uri may become invalid for other reasons, such as:
The user deleted the content
The user moved the content, and the Uri would now be different
The app that gave you the Uri is only keeping a local cache of content, and it eventually evicts this content out of that cache, requiring it to be downloaded again
What do I am wrong?
You are assuming that a Uri lives forever.
You can try takePersistableUriPermission(). This will require a minSdkVersion of 19 (Android 4.4+), may not work on many Uri values (because the provider is not offering persistable permissions), and it will not solve all of the problems (e.g., user deleted the content).
Or, you can make your own local copy of the content as soon as you get a Uri, rather than relying upon Uri values from other apps.

java.io.File and new Lollipop SDCard Access API can't work together?

I've followed this How to use the new SD card access API presented for Android 5.0 (Lollipop)? but still not satisfy my problem.
My application is almost similar to ES Explorer, where it will display all files to edit. When user finish editing the file, it will save the changes through this call OtherPartyImageSaver.save(File f, Metadata updatedMetadata)
I tried the following, but failed:
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
OtherPartyImageSaver.save(new File("/storage/sdcard/Download/hello.jpg"),
updatedMetadata);
This class doesn't work with OutputStream.
Do I have to give up on OtherPartyImageSaver or is there any way to achieve this?
If I have to give up with the library, can Uri from other provider be granted for modification?
Uri mediaUri = Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
"45");
Because the Uri _ID is retrieved by querying database.
Do I have to give up on OtherPartyImageSaver
If it only works with File, and you want to support removable storage, then either you will have to give up on OtherPartyImageSaver, or you will have to modify it to support streams (if the class is open source).

Categories

Resources