How can I verify image URI is valid in Android? - 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
}

Related

Retrieve EXIF data from gallery image in onActivityResult

Currently I am getting an image back from the gallery and populating an imageField with it. I am attempting to pull location information from the image, assuming it exists, but all I keep getting is null values.
I know the image I am testing with has entries for latitude and longitude in its EXIF data.
I've tried everything I can find online and nothing is working. I believe the problem lies in the improper parameter being sent to the ExifInterface. Here's what I've got right now:
...
else if (requestCode == REQUEST_IMAGE_FROM_GALLERY) {
try {
Uri selectedImage = data.getData();
String currentImage = selectedImage.getPath()
+ File.separator + getFileName(selectedImage);
....
Then initialize the ExifInterface:
....
try {
ExifInterface ei = new ExifInterface(currentImage);
imageLatitude = ei.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
imageLongitude = ei.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
....
But imageLatitude and imageLongitude are returning null. I've even gone so far as to replace the parameter sent into ExifInterface as the full path to the image (/storage/emulated/0/DCIM/Camera/IMG_20150119_170010.jpg) but still getting null values.
What exactly is supposed to be passed into ExifInterface and, if I'm passing in the correct parameter, why am I getting null values?
I ended up getting it to work using this method:
private String getRealPathFromURI(Uri contentURI, Activity activity) {
Cursor cursor = activity.getContentResolver()
.query(contentURI, null, null, null, null);
if (cursor == null) { // Source is Dropbox or other similar local file
// path
return contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
return cursor.getString(idx);
}
}
See it in use here:
if (requestCode == REQUEST_IMAGE_FROM_GALLERY) {
try {
Uri selectedImage = data.getData();
String currentImageFile = getRealPathFromURI(selectedImage, this);
ExifInterface ei = new ExifInterface(currentImageFile);
This provided a proper ExifInterface that allowed me to pull the desired data from the image (where it existed).
Thanks for the input everyone.
Maybe the image path you passed to ExifInterface is invalid, you can check it by using the code:
File file = new File(currentImage);
if(file.exists()){
// print file exist
}else{
// print file not exist.
}
If it's the problem, you can solve it by pass a valid image path. If it's not the problem you can debug your code and see the attributes in the ExifInterface ei object by adding a breakpoint before the following code:
imageLatitude = ei.getAttribute(ExifInterface.TAG_GPS_LATITUDE);

Android Gallery doesn't support streamed files

In my app the user can select files and open them in the appropriate app (using an ACTION_VIEW intent).
I need to do some work on the data before giving it to the other app. So I'm using a streaming solution : I implemented a ContentProvider that implements openTypedAssetFile and writeDataToPipe (this method fills the output ParcelFileDescriptor created by openPipeHelper).
This works : I can open .pdf files, .txt files etc. The streaming seems correct.
I can open images usings 3-party apps.
However when I open an image using the Gallery, it doesn't work (Gallery shows a black screen), and I get the following exception :
fail to open myfile.jpg
UriImage(21890): java.io.FileNotFoundException: Not a whole file
I had a look at the Gallery source (here) and I could see that the exception is thrown here :
try {
if (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) {
InputStream is = mApplication.getContentResolver()
.openInputStream(mUri);
mRotation = Exif.getOrientation(is);
Utils.closeSilently(is);
}
**mFileDescriptor = mApplication.getContentResolver()
.openFileDescriptor(mUri, "r");**
if (jc.isCancelled()) return STATE_INIT;
return STATE_DOWNLOADED;
} catch (FileNotFoundException e) {
Log.w(TAG, "fail to open: " + mUri, e);
return STATE_ERROR;
}
However, once in the Gallery app, if I select "Set as wallpaper", then I can see my image, it is then well streamed. So the problem appears when Gallery opens.
After a deeper look (in the ContentResolver code etc.) I couldn't understand why it behaves this way. It seems that Gallery does not support streaming files. Is that right ?
Do you have any idea ?
Thanks a lot.
String scheme = mUri.getScheme();
ContentResolver contentResolver = getContentResolver();
// If we are sent file://something or
// content://org.openintents.filemanager/mimetype/something...
if (scheme.equals("file")
|| (scheme.equals("content") && fileUri
.getEncodedAuthority().equals(
"org.openintents.filemanager"))) {
// Get the path
filePath = fileUri.getPath();
// Trim the path if necessary
// openintents filemanager returns
// content://org.openintents.filemanager/mimetype//mnt/sdcard/xxxx.jpg
if (filePath.startsWith("/mimetype/")) {
String trimmedFilePath = filePath
.substring("/mimetype/".length());
filePath = trimmedFilePath.substring(trimmedFilePath
.indexOf("/"));
}
} else if (scheme.equals("content")) {
// If we are given another content:// URI, look it up in the
// media provider
filePath = getFilePathFromContentUri(fileUri,
contentResolver);
} else {
Log.e("filePath--------->>>>>", filePath + "");
}
and
private String getFilePathFromContentUri(Uri selectedVideoUri,
ContentResolver contentResolver) {
String filePathString;
String[] filePathColumn = { MediaColumns.DATA };
Cursor cursor = contentResolver.query(selectedVideoUri, filePathColumn,
null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
filePathString = cursor.getString(columnIndex);
cursor.close();
return filePathString;
}
and now use this filepath ..
try {
if (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) {
InputStream is = mApplication.getContentResolver()
.openInputStream(filePath); // ** change is here
mRotation = Exif.getOrientation(is);
Utils.closeSilently(is);
}
** mFileDescriptor = mApplication.getContentResolver()
.openFileDescriptor(filePath, "r"); **
if (jc.isCancelled()) return STATE_INIT;
return STATE_DOWNLOADED;
} catch (FileNotFoundException e) {
Log.w(TAG, "fail to open: " + mUri, e);
return STATE_ERROR;
}
this is work for me

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 to change the default folder the SD card saves to?

My application current saves to a folder I've specified /Pictures/<app_name>/ but it also saves to the default folder /DCIM/Camera/. This results in the file being saved to the SD card twice but in different folders. How do I make it so it doesn't save it to both folders, only the folder I specify?
The answer was already get Here.
Just go to the original answer ---> HERE THE SOLUTION
I just add the solution to avoid a invalid answer if linked page changes.
Check the following code:
private void FillPhotoList() {
// initialize the list!
GalleryList.clear();
String[] projection = { MediaStore.Images.ImageColumns.DISPLAY_NAME };
for(int i=0;i<projection.length;i++)
Log.i("InfoLog","projection "+projection[0].toString());
// intialize the Uri and the Cursor, and the current expected size.
Cursor c = null;
Uri u = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Log.i("InfoLog","FillPhoto Uri u "+u.toString());
// Query the Uri to get the data path. Only if the Uri is valid.
if (u != null)
{
c = managedQuery(u, projection, null, null, null);
}
// If we found the cursor and found a record in it (we also have the id).
if ((c != null) && (c.moveToFirst()))
{
do
{
// Loop each and add to the list.
GalleryList.add(c.getString(0)); // adding all the images sotred in the mobile phone(Internal and SD card)
}
while (c.moveToNext());
}
Log.i(INFOLOG,"gallery size "+ GalleryList.size());
}
and this is where the method is doing all magic
/** Method will check all the photo is the gallery and delete last captured and move it to the required folder.
*/
public void movingCapturedImageFromDCIMtoMerchandising()
{
// This is ##### ridiculous. Some versions of Android save
// to the MediaStore as well. Not sure why! We don't know what
// name Android will give either, so we get to search for this
// manually and remove it.
String[] projection = { MediaStore.Images.ImageColumns.SIZE,
MediaStore.Images.ImageColumns.DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATA,
BaseColumns._ID,};
// intialize the Uri and the Cursor, and the current expected size.
for(int i=0;i<projection.length;i++)
Log.i("InfoLog","on activityresult projection "+projection[i]);
//+" "+projection[1]+" "+projection[2]+" "+projection[3] this will be needed if u remove the for loop
Cursor c = null;
Uri u = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Log.i("InfoLog","on activityresult Uri u "+u.toString());
if (CurrentFile != null)
{
// Query the Uri to get the data path. Only if the Uri is valid,
// and we had a valid size to be searching for.
if ((u != null) && (CurrentFile.length() > 0))
{
//****u is the place from data will come and projection is the specified data what we want
c = managedQuery(u, projection, null, null, null);
}
// If we found the cursor and found a record in it (we also have the size).
if ((c != null) && (c.moveToFirst()))
{
do
{
// Check each area in the gallery we built before.
boolean bFound = false;
for (String sGallery : GalleryList)
{
if (sGallery.equalsIgnoreCase(c.getString(1)))
{
bFound = true;
Log.i("InfoLog","c.getString(1) "+c.getString(1));
break;
}
}
// To here we looped the full gallery.
if (!bFound) //the file which is newly created and it has to be deleted from the gallery
{
// This is the NEW image. If the size is bigger, copy it.
// Then delete it!
File f = new File(c.getString(2));
// Ensure it's there, check size, and delete!
if ((f.exists()) && (CurrentFile.length() < c.getLong(0)) && (CurrentFile.delete()))
{
// Finally we can stop the copy.
try
{
CurrentFile.createNewFile();
FileChannel source = null;
FileChannel destination = null;
try
{
source = new FileInputStream(f).getChannel();
destination = new FileOutputStream(CurrentFile).getChannel();
destination.transferFrom(source, 0, source.size());
}
finally
{
if (source != null)
{
source.close();
}
if (destination != null)
{
destination.close();
}
}
}
catch (IOException e)
{
// Could not copy the file over.
ToastMaker.makeToast(this, "Error Occured", 0);
}
}
//****deleting the file which is in the gallery
Log.i(INFOLOG,"imagePreORNext1 "+imagePreORNext);
Handler handler = new Handler();
//handler.postDelayed(runnable,300);
Log.i(INFOLOG,"imagePreORNext2 "+imagePreORNext);
ContentResolver cr = getContentResolver();
cr.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, BaseColumns._ID + "=" + c.getString(3), null);
break;
}
}
while (c.moveToNext());
}
}
}
For Android Gingerbread version 2.3.3 you can change the cameras default save location. Launch the camera app, click on settings (cog), click on wrench tab, scroll down then select Memory card. Change to Memory card (versus Internal memory).

How to check if resource pointed by Uri is available?

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.

Categories

Resources