How to check if resource pointed by Uri is available? - android

I have resource (music file) pointed by Uri. How can I check if it is available before I try to play it with MediaPlayer?
Its Uri is stored in database, so when the file is deleted or on external storage that is unmounted, then I just get exception when I call MediaPlayer.prepare().
In above situation I would like to play systems default ringtone. I could of course do that after I catch above exception, but maybe there is some more elegant solution?
edit:
I forgot to mention that music files Uri's are actually acquired by using RingtonePreference. This means that I can get Uri pointing to ringtone on Internal Storage, External Storage or to default systems ringtone.
Uri's examples are:
content://settings/system/ringtone - for choosing default ringtone
content://media/internal/audio/media/60 - for ringtone on Internal Storage
content://media/external/audio/media/192 - for ringtone on External Storage
I was happy with proposed "new File(path).exists() method, as it saved me from mentioned exception, but after some time I noticed that it returns false for all of my ringtone choices...
Any other ideas?

The reason the proposed method doesn't work is because you're using the ContentProvider URI rather than the actual file path. To get the actual file path, you have to use a cursor to get the file.
Assuming String contentUri is equal to the content URI such as content://media/external/audio/media/192
ContentResolver cr = getContentResolver();
String[] projection = {MediaStore.MediaColumns.DATA}
Cursor cur = cr.query(Uri.parse(contentUri), projection, null, null, null);
if (cur != null) {
if (cur.moveToFirst()) {
String filePath = cur.getString(0);
if (new File(filePath).exists()) {
// do something if it exists
} else {
// File was not found
}
} else {
// Uri was ok but no entry found.
}
cur.close();
} else {
// content Uri was invalid or some other error occurred
}
I haven't used this method with sound files or internal storage, but it should work. The query should return a single row directly to your file.

I too had this problem - I really wanted to check if a Uri was available before trying to load it, as unnecessary failures would end up crowding my Crashlytics logs.
Since the arrival of the StorageAccessFramework (SAF), DocumentProviders, etc., dealing with Uris has become more complicated. This is what I eventually used:
fun yourFunction() {
val uriToLoad = ...
val validUris = contentResolver.persistedUriPermissions.map { uri }
if (isLoadable(uriToLoad, validUris) != UriLoadable.NO) {
// Attempt to load the uri
}
}
enum class UriLoadable {
YES, NO, MAYBE
}
fun isLoadable(uri: Uri, granted: List<Uri>): UriLoadable {
return when(uri.scheme) {
"content" -> {
if (DocumentsContract.isDocumentUri(this, uri))
if (documentUriExists(uri) && granted.contains(uri))
UriLoadable.YES
else
UriLoadable.NO
else // Content URI is not from a document provider
if (contentUriExists(uri))
UriLoadable.YES
else
UriLoadable.NO
}
"file" -> if (File(uri.path).exists()) UriLoadable.YES else UriLoadable.NO
// http, https, etc. No inexpensive way to test existence.
else -> UriLoadable.MAYBE
}
}
// All DocumentProviders should support the COLUMN_DOCUMENT_ID column
fun documentUriExists(uri: Uri): Boolean =
resolveUri(uri, DocumentsContract.Document.COLUMN_DOCUMENT_ID)
// All ContentProviders should support the BaseColumns._ID column
fun contentUriExists(uri: Uri): Boolean =
resolveUri(uri, BaseColumns._ID)
fun resolveUri(uri: Uri, column: String): Boolean {
val cursor = contentResolver.query(uri,
arrayOf(column), // Empty projections are bad for performance
null,
null,
null)
val result = cursor?.moveToFirst() ?: false
cursor?.close()
return result
}
If someone has a more elegant -- or correct -- alternative, please do comment.

Try a function like:
public static boolean checkURIResource(Context context, Uri uri) {
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
boolean doesExist= (cursor != null && cursor.moveToFirst());
if (cursor != null) {
cursor.close();
}
return doesExist;
}

For those still looking out for a solution [works perfectly fine as of Dec 2020] and behaves as expected for all edge cases, the solution is a follows:
boolean bool = false;
if(null != uri) {
try {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
inputStream.close();
bool = true;
} catch (Exception e) {
Log.w(MY_TAG, "File corresponding to the uri does not exist " + uri.toString());
}
}
If the file corresponding to the URI exists, then you will have an input stream object to work with, else an exception will be thrown.
Do not forget to close the input stream if the file does exist.
NOTE:
DocumentFile sourceFile = DocumentFile.fromSingleUri(context, uri);
boolean bool = sourceFile.exists();
The above lines of code for DocumentFile, does handle most edge cases, but what I found out was that if a file is created programmatically and stored in some folder, the user then visits the folder and manually deletes the file (while the app is running), DocumentFile.fromSingleUri wrongly says that the file exists.

As of Kitkat you can, and you should, persist URIs that your app uses if necessary. As far as I know, there's a 128 URI limit you can persist per app, so it's up to you to maximize usage of those resources.
Personally I wouldn't deal with direct paths in this case, but rather check if persisted URI still exists, since when resource (a file) is deleted from a device, your app loses rights to that URI therefore making that check as simple as the following:
getContext().getContentResolver().getPersistedUriPermissions().forEach( {element -> element.uri == yourUri});
Meanwhile you won't need to check for URI permissions when a device is below Kitkat API level.
Usually, when reading files from URIs you're going to use ParcelFileDescriptor, thus it's going to throw if no file is available for that particular URI, therefore you should wrap it with try/catch block.

🍎 2021-08-17 10:53:50
GitHub 👉 https://github.com/javakam/FileOperator/blob/master/library_core/src/main/java/ando/file/core/FileUtils.kt#L317
基于热评第一的方案, 给出我的解决方式(兼容Q,P):
Based on the hottest solution, give me a solution (compatible with Q, P):
/**
* 1. 检查 Uri 是否正确
* 2. Uri 指向的文件是否存在 (可能是已删除, 也肯是系统 db 存有 Uri 相关记录, 但是文件失效或者损坏)
*
* 1. Check if Uri is correct
* 2. Whether the file pointed to by Uri exists (It may be deleted, it is also possible that the system db has Uri related records, but the file is invalid or damaged)
*
* https://stackoverflow.com/questions/7645951/how-to-check-if-resource-pointed-by-uri-is-available
*/
fun checkRight(uri: Uri?): Boolean {
if (uri == null) return false
val resolver = FileOperator.getContext().contentResolver
//1. Check Uri
var cursor: Cursor? = null
val isUriExist: Boolean = try {
cursor = resolver.query(uri, null, null, null, null)
//cursor null: content Uri was invalid or some other error occurred
//cursor.moveToFirst() false: Uri was ok but no entry found.
(cursor != null && cursor.moveToFirst())
} catch (t: Throwable) {
FileLogger.e("1.Check Uri Error: ${t.message}")
false
} finally {
try {
cursor?.close()
} catch (t: Throwable) {
}
}
//2. Check File Exist
//如果系统 db 存有 Uri 相关记录, 但是文件失效或者损坏 (If the system db has Uri related records, but the file is invalid or damaged)
var ins: InputStream? = null
val isFileExist: Boolean = try {
ins = resolver.openInputStream(uri)
// file exists
true
} catch (t: Throwable) {
// File was not found eg: open failed: ENOENT (No such file or directory)
FileLogger.e("2. Check File Exist Error: ${t.message}")
false
} finally {
try {
ins?.close()
} catch (t: Throwable) {
}
}
return isUriExist && isFileExist
}

There are few methods of DocumentProvider when applied to uri, return null if there is no document underlying uri. I chose getType(uri) which returns mime type of the document/file. If no document/file exists represented in uri, it returns null. Hence, to detect whether documennt/file exists or not, you can use this method like below.
public static boolean exists(Context context, Uri uri)
{
return context.getContentResolver().getType(uri) !=null;
}
Other methods mentioned like querying the uri to get documentID or opening inputstream/outputstream did not work because they throw filenotfound exception if document/file does not exist, which resulted in crashing of app.
You may attempt other methods which return null instead of throwing filenotfoundexception, if document/file does not exist.

Related

Can we delete an image file using MediaStore API? if yes then how

I have a requirement to delete screenshot image file after a certain time using background service in my app and it was working fine using the above method
private void deleteTheFile(String path) {
File fdelete = new File(path);
if (fdelete.exists()) {
if (fdelete.delete()) {
getApplicationContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(path))));
Log.i(TAG, "deleteTheFile: file deleted");
} else {
Log.i(TAG, "deleteTheFile: file not dellleeettteeeddd");
}
}
But as everyone knows about the changes which came with android R (11)
So I tried to update my app with
MANAGE_EXTERNAL_STORAGE permission
But Google rejected my update saying
Issue: Need to use Media Store API
You have requested access to All Files Access permission but it
appears that your app's core feature requires access to only Media
Files. With the MediaStore API, apps can contribute and access media
that's available on an external storage volume without the need for
the access all files permission.
Please update your app so that the feature uses Media Store APIs and
remove All Files Access (MANAGE_EXTERNAL_STORAGE) permission.
But I have never worked with media store API before and I don't know can it delete an image file with it, because deleting a file comes under writeable section
Using createDeleteRequest
private fun deleteImages(uris: List<Uri>) {
val pendingIntent = MediaStore.createDeleteRequest(contentResolver, uris.filter {
checkUriPermission(it, Binder.getCallingPid(), Binder.getCallingUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PackageManager.PERMISSION_GRANTED
})
startIntentSenderForResult(pendingIntent.intentSender, REQ_CODE, null, 0, 0, 0)
}
using contentResolver
// Remove a specific media item.
val resolver = applicationContext.contentResolver
// URI of the image to remove.
val imageUri = "..."
// WHERE clause.
val selection = "..."
val selectionArgs = "..."
// Perform the actual removal.
val numImagesRemoved = resolver.delete(
imageUri,
selection,
selectionArgs)
https://github.com/android/storage-samples/tree/main/MediaStore
This is an android official sample you can follow to have an understanding and try to implement it using MediaStoreAPI
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val pendingIntent = MediaStore.createDeleteRequest(context.contentResolver, getImageDeleteUri(context, filePath))
context.startIntentSenderForResult(pendingIntent.intentSender, requestCode, null, 0, 0, 0)}
//finally handle it's result in onActivityResult
getting delete uri from image path:
fun getImageDeleteUri(context: Context, path: String): Uri? {
val cursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + " = ?",
arrayOf(path),
null
)
val uri = if (cursor != null && cursor.moveToFirst())
ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
) else null
cursor?.close()
return uri
}

Is there a way to get 100% correct file extension in Android with scoped storage enabled?

I'm trying to implement a file sending functionality in my Android app (any files are allowed, and the files don't belong to my app). From the ACTION_OPEN_DOCUMENT I receive an InputStream, then I make a temp File object with the name I'm getting from ContentResolver's OpenableColumns.DISPLAY_NAME, and then send the file. The reason I do all of this is that I work with a 3rd party API which allows for File objects only.
But the OpenableColumns.DISPLAY_NAME doesn't guarantee that I get the file name with a file extension as stated in the docs. As far as I understand, there is no way to get the actual filename or physical path of a file with the Scoped Storage enforced in the newer versions of Android. Therefore, I have to check if a filename contains an extension, and if not - get the file's MIME type with ContentResolver and the most common extension for it using the MimeTypeMap. This approach feels to be not very reliable since I have to rely on both ContentResolver correctly determining the MIME type and MimeTypeMap retrieving the correct extension. Getting the extension is crucial at least because users should be able to download and open files on their PC from a desktop app.
So, is it possible to get a filename or at least file extension with a 100% guarantee with scoped storage enabled? Or maybe is there a more efficient way to handle my situation? I'd appreciate some help with this.
Try this method, it helped me:
public static String getFileName(Uri uri, Context context) {
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}

Andriod 10 How to check file exist

In Android Q, save pictures in app-specific directory,
path like = /data/user/0/xxx.xxx.xxx/files/phone/abc.jpg
not save in the external storage, use Device FileExplorer to view,
need to check if file exist, avoid to download again
,but in Android Q file.exist() not work
File newFile = new File(path);
newFile.exists();
always return false
this question. I need to use MediaStore or SAF to resolver it.
or other function to check it.
If I use MediaStore to check. use ContentResolver. May be like this:
public void getPhotoCursor(Uri uri) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
But I can't get the Uri form app-specific directory. If I get the Uri, how to use file descriptor to check.
or use SAF to check.
File testFile = new File(getExternalFilesDir()+"phone", "abc.jpg");
FileProvider.getUriForFile(,,testFile);
Intent testIntent = new Intent(Intent.ACTION_VIEW);
testIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
testIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
testIntent.setDataAndType();
startActivity(testIntent);
In the ActivityResult to check it
Any help will be apperciated
is my fault, every time open APP I will delete all the .jpg from APP-specific.
so into APP I want to check avoid download again. file exist always return false.

Get real path from an audio Uri

In my app the user can choose a notification using RingtonePreference. From the latter I'm able to retrieve the Uri of the selected notification, and using the following code to extract the real file name:
private String getUriRealPath(Uri contentUri) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Getting real path of uri: " + contentUri.toString());
}
String path = null;
final String[] projection = new String [] { MediaStore.Audio.Media.DATA };
final Cursor cursor;
try {
cursor = mContext.getContentResolver().query(contentUri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int idx = cursor.getColumnIndex(projection[0]);
if (idx != -1) {
path = cursor.getString(idx);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Real path is: " + path);
}
}
else {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Path can't be resolved.");
}
}
}
} catch (Exception e) {
Log.e(TAG, "getUriRealPath - " + e);
}
return path;
}
However, once the user chooses a notification that was downloaded via a 3-rd party, the above code can't find the real path.
The reason for the extraction is I need the path for playing the notification in a SoundPool object.
I may be over seeing this, but getContentResolver() returns a ContentResolver instance for my application. Should I be using a "global" ContentResolver ?
However, once the user chooses a notification that was downloaded via a 3-rd party, the above code can't find the real path.
You may not have access to the actual file, anyway. First, it may not exist as a file, but only as a stream. Second, it may not be in storage for which you have read access. You can only reliably access this media via the Uri supplied to you.
The reason for the extraction is I need the path for playing the notification in a SoundPool object.
Then you will have to stop using SoundPool and switch to MediaPlayer, AudioTrack, or something else.
Should I be using a "global" ContentResolver ?
There is no "global" ContentResolver.

How can I verify image URI is valid in Android?

I am building my own contact picker, because I needed multi-select support. Everything is working fine, except for one small problem with the contact images.
For contacts who don't have images I am showing a "no image" image. This works fine for contacts in the phone's address book. I am having a problem however when it comes to images from my google contacts.
Most of my google contacts do not have photos. However, when i query the Contacts database for photos, it still returns a URI for them of the form of content://com.android.contacts/contacts/657/photo (which is the same format as for contacts who do have a photo.
Then when I try to assign the photo to a QuickContactBadge, using bdg.setImageURI(pic); it sets it to essentially a blank picture, and logs a silent INFO message stating:
INFO/System.out(3968): resolveUri failed on bad bitmap uri:
content://com.android.contacts/contacts/657/photo
I need to know how I can either
a) validate the URI or
b) catch the INFO message above
c) query the imageview/badge to see if it found a valid image
so that i can assign these contacts my "no image" image.
How can I go about doing this?
EDIT 20110812.0044
I have tried adding this to my code as per Laurence's suggestion (which he's since removed):
// rv is my URI variable
if(rv != null) {
Drawable d = Drawable.createFromPath(rv.toString());
if (d == null) rv = null;
}
While the google contacts now get my "no image" image, ... so do all the other contacts, including ones that do in fact have images.
Okay, I figured out how to do this after poking through the ImageView source code. It is actually using the QuickContactBadge's own methods, but if necessary, one could always extract the relevant code from the Badge/ImageView control here.
After setting the QCB's image, I check to see if its drawable is null, instead of trying my own (as per Laurence's suggestion). This works better, because there is actually a whole slew of checking code the ImageView widget uses.
Here is my final code:
bdg.setImageURI(pic);
if(bdg.getDrawable() == null) bdg.setImageResource(R.drawable.contactg);
This works perfectly as I was hoping and expecting.
Just to answer the question on how to check the (data) value in the MediaStore:
ContentResolver cr = getContentResolver();
String[] projection = {MediaStore.MediaColumns.DATA}
Cursor cur = cr.query(Uri.parse(contentUri), projection, null, null, null);
if(cur != null) {
cur.moveToFirst();
String filePath = cur.getString(0);
if (filePath == null || filePath.isEmpty()) {
// data not set
} else if((new File(filePath)).exists()){
// do something if it exists
} else {
// File was not found
// this is binary data
}
} else {
// content Uri was invalid or some other error occurred
}
Inspiration taken from: https://stackoverflow.com/a/7649784/621690 and others.
There is also the column SIZE that might be checked: http://developer.android.com/reference/android/provider/MediaStore.MediaColumns.html#SIZE
It sounds like it should contain 0 if there is no data value. But I wouldn't know what it contains if data is a file path.
It could be that the images are not downloaded. I faced a similar problem with whatsapp images.
One way to go about this could be like below:
InputStream is = null;
try {
is = context.getContentResolver().openInputStream(myuri);
}catch (Exception e){
Log.d("TAG", "Exception " + e);
}
if(is==null)
//Assign to "no image"
Based on the code (http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/widget/ImageView.java) my solution for checking Uri:
public static Uri checkUriExists (Context mContext,Uri mUri) {
Drawable d = null;
if (mUri != null) {
if ("content".equals(mUri.getScheme())) {
try {
d = Drawable.createFromStream(
mContext.getContentResolver().openInputStream(mUri),
null);
} catch (Exception e) {
Log.w("checkUriExists", "Unable to open content: " + mUri, e);
mUri = null;
}
} else {
d = Drawable.createFromPath(mUri.toString());
}
if (d == null) {
// Invalid uri
mUri = null;
}
}
return mUri;
}
I am using this code for Uri that has file:// authority
Uri resimUri = Uri.parse(path_str);
File imgFile = new File(resimUri.getPath());
if (imgFile.exists()) {
// file exists
}else {
// file is not there
}

Categories

Resources