I'm reading thumbnails from the device by querying the MediaStore, using MediaStore.Images.Thumbnails.getThumbnail(). However, this has been deprecated in Android 10 (API 29), with a pointer to ContentResolver#loadThumbnail: https://developer.android.com/reference/android/provider/MediaStore.Images.Thumbnails
However, I can't get this to work (in an emulated device running API 29). I've copied some JPEGs onto the emulated device, which end up in the Downloads folder. They show up fine in the Photos app. The following code gives me a FileNotFoundException. What does "No content provider" actually tell me?
try {
Size thumbSize = new Size(100, 100);
Uri thumbUri = Uri.fromFile(new File(imgPath));
// imgPath: /storage/emulated/0/Download/pexels-photo-323490.jpeg
// thumbUri: file:///storage/emulated/0/Download/pexels-photo-323490.jpeg
Bitmap thumbBitmap;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
thumbBitmap = mContext.getContentResolver().loadThumbnail(thumbUri, thumbSize, null);
} else {
thumbBitmap = MediaStore.Images.Thumbnails.getThumbnail(mContext.getContentResolver(),
imgId, MediaStore.Images.Thumbnails.MINI_KIND, null);
}
iconView.setImageBitmap(thumbBitmap);
} catch (Exception e) {
Log.e("err", e.toString());
}
Exception:
java.io.FileNotFoundException: No content provider: file:///storage/emulated/0/Download/pexels-photo-323490.jpeg
Please try this, hope it works for You:
int thumbColumn = cur.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID);
int _thumpId = cur.getInt(thumbColumn);
Uri imageUri_t = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,_thumpId);
GGK
The best Answer for getting Thumbnail and All Android Versions is this:
val thumbnail: Bitmap = if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)) {
mContentResolver.loadThumbnail(contentUri, Size(width / 2, height / 2), null)
} else {
MediaStore.Images.Thumbnails.getThumbnail(mContentResolver, id, MediaStore.Images.Thumbnails.MINI_KIND, null)
}
The uri may not be good. I mean try using FileProvider to get uri of the file. If your legecyExternalStorage is false, then you can not access file like this. The same goes for android 12. You need to use MediaStore to get the contentUri
Related
I have a chat application. If a user sends an image i save that image to internal storage of the app under
data/data/package_name/....
So if the user clicks on that image i send an intent to the system to open it
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, File(it.localUri))
val intent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(uri, "image/*")
}
startActivity(intent)
The problem is that in the image viewer there is no choice to save the file in the public storage of the phone, in that case in the gallery.
Is there any way to do that without changing the internal storage default save location of the images in my app?
If this is something you are interested in, you can use MediaStore to programmatically save in the gallery a picture from your internal storage.
Code
You could use the following code, which I successfully use in my app.
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "MyPicture.jpg");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
}
ContentResolver resolver = myContext.getContentResolver();
Bitmap bitmap;
Uri uri;
try {
// Requires permission WRITE_EXTERNAL_STORAGE
bitmap = BitmapFactory.decodeFile(currentPhotoPath);
uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
} catch (Exception e) {
Log.e(LOG_TAG, "Error inserting picture in MediaStore: " + e.getMessage());
return;
}
try (OutputStream stream = resolver.openOutputStream(uri)) {
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream) {
throw new IOException("Error compressing the picture.");
}
} catch (Exception e) {
if (uri != null) {
resolver.delete(uri, null, null);
}
Log.e(LOG_TAG, "Error adding picture to gallery: " + e.getMessage());
}
Credits to this answer for the conversion of File to Bitmap and to this answer for the usage of  MediaStore.
Notes
BitmapFactory.decodeFile() requires the runtime permission WRITE_EXTERNAL_STORAGE
With the introduction of scoped memory in Android 10 (which will become mandatory in Android 11, see this article) MediaStore is probably the only reliable way to save pictures to the gallery
A consequence of scoped storage is that, yes, you should keep using the internal storage for the cache copy of your picture.
Tests
I have tested this code with with targetSdkVersion 29 on the following devices / OS combinations
Samsung Galaxy S10 / API 29
Samsung Galaxy S9 / API 29
Huawei Nexus 6P / API 27
I'm trying to get my file handling sorted for Android API 29. Since I'm no star in anything related to files on Android, I've run into a problem.
When I add images to my app I can delete them using contentResolver.delete(deleteUri, null, null); and everything works fine. But when the app is removed and then reinstalled it gives me a RecoverableSecurityException, this is accounted for and I've made it so that permission is requested to the file to be able to delete it. When permission is granted and I'm trying to delete the file again, it still gives me
android.app.RecoverableSecurityException: **app**.debug has no access to content://media/external/images/media/280. It is removed from the ContentResolver since it's no longer visible in any of the galleries and it returns no result when queried for, but the file is still "physically" on the device.
Where do I need to look to fix this problem? There is only one result from the ContentResolver, the file path it shows in a different error(shown together with the above error) is correct: E/MediaProvider: Couldn't delete /storage/emulated/0/Pictures/**app**/**filename**.jpeg
Delete file function:
Cursor c = getCursorFromContentResolver(fileName);
if (c != null && c.moveToFirst())
{
long id = c.getLong(c.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
Uri deleteUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
try
{
contentResolver.delete(deleteUri, null, null);
}catch (RecoverableSecurityException e)
{
//After the below Intent returns, the current function is run again
activity.startIntentSenderForResult(e.getUserAction().getActionIntent().getIntentSender(), requestCode, null, 0, 0, 0, null);
}
c.close();
return true;
}
Other permissions are requested:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
I have come across a very similar issue in Android Q, when deleting an image using Content resolver delete call. It turned out that despite me catching RecoverableSecurityException for permission to delete the image from the android gallery - it still has thrown an error that it could not delete the file because the app doesn't have permission (thus after opening Google Photos it would scan for images and find the "undeleted" one making it come back). This is where I saw the same error as in your question. When I tried the same code on Android R file did not come back.
After reading on this issue I tried SAF as a solution. The way I managed to delete image for good was to get the user to select a directory of the images (in my case DCIM/Camera) using Intent.ACTION_OPEN_DOCUMENT_TREE
Then I've captured Uri of the parent folder and name of the file in that folder when delete button was pressed:
String filename = queryUriName(content, photoUri);
DocumentFile docF = DocumentFile.fromTreeUri(context,myTree);
Boolean docex = docF.exists();
String idDoc = DocumentsContract.getTreeDocumentId(myTree);
idDoc = idDoc + "/"+filename;
Uri childrenUri = DocumentsContract.buildDocumentUriUsingTree(myTree,idDoc);
DocumentFile childfile = DocumentFile.fromSingleUri(context,childrenUri);
Boolean chex = childfile.exists();
System.out.println("child exist: "+ chex+ " file name is " + filename +" "+idDoc);
This allowed to delete the actual file and from what I can tell it does not throw any errors and file is gone.
Take this with caution as I am ~3 weeks into android dev and could not find problem described nor solution elsewhere. But would be happy to expand on or update the answer if needed.
In case, if someone looking to handle RecoverableSecurityExceptionin your catch block from android 10 onwards
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if(e instanceof RecoverableSecurityException){
RecoverableSecurityException recoverableSecurityException = (RecoverableSecurityException) e;
IntentSender intentSender = recoverableSecurityException.getUserAction().getActionIntent().getIntentSender();
if(intentSender != null){
try {
startIntentSender(intentSender, null, 0, 0, 0);
} catch (IntentSender.SendIntentException sendIntentException) {
sendIntentException.printStackTrace();
}
}
}
}
I am trying to store an image, which is located in my project's drawable folder to the gallery of my Samsung Android tablet. I have been able to successfully achieve this for an HTC phone using the exact same code; however, it appears when attempting this for a Samsung tablet, the URI I am using to insert the image is appearing not to exist. The error appears to occur when I attempt to open the ouput stream via the line...
OutputStream imageOut = getContentResolver().openOutputStream(url);
As mentioned before, this exact code worked for an HTC android phone, which leads me to believe something is different in the file system of the two devices as to where to store the image. The full code I am using (which is mainly copied from the MediaStore class) is found below.
Drawable drawable = getResources().getDrawable(R.drawable.sample);
Bitmap bitmapImage = ((BitmapDrawable)drawable).getBitmap();
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, "demo_image");
values.put(MediaStore.Images.Media.DESCRIPTION, "sample image");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri url = null;
try {
url = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
if (bitmapImage != null) {
OutputStream imageOut = getContentResolver().openOutputStream(url);
try {
bitmapImage.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
} finally {
imageOut.close();
}
long id = ContentUris.parseId(url);
// Wait until MINI_KIND thumbnail is generated.
Bitmap miniThumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(), id,
MediaStore.Images.Thumbnails.MINI_KIND, null);
// This is for backward compatibility.
// Bitmap microThumb = MediaStore.Images.Thumbnail(getContentResolver(), miniThumb, id, 50F, 50F,
// MediaStore.Images.Thumbnails.MICRO_KIND);
} else {
//Log.e(TAG, "Failed to create thumbnail, removing original");
getContentResolver().delete(url, null, null);
url = null;
}
} catch (Exception e) {
//Log.e(TAG, "Failed to insert image", e);
if (url != null) {
getContentResolver().delete(url, null, null);
url = null;
}
}
In my application i have to show list of videos,i have created image(thumb) from video and show that image in list.
I added code that i was used for create thumb-
Bitmap bitmap=ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.FULL_SCREEN_KIND);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
My problem is that when i run app on android api level below 4.0 thumb is generate but when i run same application on android 4.0 and above ThumbnailUtils.createVideoThumbnail() method returns null.
Please help me how to fix this issue.
Working from yesterday but still not getting solution.I have tried -
Bitmap bitmap=ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND);
and
Bitmap bitmap=ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MICRO_KIND);
but still returning null.
Thanks in advance.
createVideoThumbnail(String filePath, int kind) supports MINI_KIND or MICRO_KIND as kind only.
see http://developer.android.com/reference/android/media/ThumbnailUtils.html.
try one of those...
regards
note: May return null if the video is corrupt or the format is not supported.
You can use the following function to get a bitmap from a video URL.
public Bitmap retriveVideoFrameFromVideo(String videoPath){
Bitmap bitmap = null;
MediaMetadataRetriever mediaMetadataRetriever = null;
try {
mediaMetadataRetriever = new MediaMetadataRetriever();
if (Build.VERSION.SDK_INT >= 14)
// no headers included
mediaMetadataRetriever.setDataSource(videoPath, new HashMap<String, String>());
else
mediaMetadataRetriever.setDataSource(videoPath);
bitmap = mediaMetadataRetriever.getFrameAtTime();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mediaMetadataRetriever != null)
mediaMetadataRetriever.release();
}
return bitmap;
}
Some devices can't play and can't create thumbnails for videos, that placed on internal memory.
Check it, and move your video to SD Card before thumbnails creating.
Here is my solution to fix this problem-
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(filePath,
MediaStore.Images.Thumbnails.MINI_KIND);
Hope this will fix your problem.
If your video located on the external storage requires permission in manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
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
}