I recently upgraded the app's target version to API 29. Due to the scoped storage in Android 10, i used MediaStore API to store and retrieve images from app external storage. Earlier, i used getExternalStoragePublicDirectory to store images taken through camera, now i use MediaStore.Images.Media.EXTERNAL_CONTENT_URI to write file to an external storage location.
Issue i am facing now is,
When i open my app and take pictures, it stores under a folder name that i gave 'myapp' and i can retrieve my images through Mediastore cursor and show them in a custom gallery. And when i uninstall my app 'myapp' folder still exists. And when i install my app again and try to read the images from the gallery, cursor is not returning any image. But if i take picture again, then i could load them to my custom gallery. Custom gallery view is just a row of images at the bottom of the screen, so that user doesn't have to browse through the photos folder to load the image to the app.
This is how i store my images in the MediaStore
Content values:
String RELATIVE_PATH = Environment.DIRECTORY_PICTURES + File.separator + "myApp";
final ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, generateImageName(new Date()));
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, RELATIVE_PATH);
Generate Name method:
int sameSecondCount;
protected String generateName(Date now)
{
String result = formatter.format(now);
long nowMillis = now.getTime();
if (nowMillis / 1000 == lastMillis / 1000)
{
sameSecondCount++;
result += "_" + sameSecondCount;
}
else
sameSecondCount = 0;
lastMillis = nowMillis;
return result + PICTURE_EXTENSION_JPG;
}
#WorkerThread
private Uri writePictureToFile(ContentValues contentValues, byte[] bitmapBytes) throws IOException
{
final ContentResolver resolver = getApplication().getContentResolver();
Uri uri = null;
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
try
{
uri = resolver.insert(contentUri, contentValues);
if (uri == null)
throw new IOException("Failed to create new MediaStore record.");
OutputStream stream = resolver.openOutputStream(uri);
if (stream == null)
{
throw new IOException("Failed to get output stream.");
}
stream.write(bitmapBytes);
}
catch (IOException e)
{
// Delete the content from the media store
if (uri != null)
resolver.delete(uri, null, null);
throw e;
}
return uri;
}
Reading images
{
String selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + " in (?,?,?)";
String[] args = new String[]{
MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg"),
MimeTypeMap.getSingleton().getMimeTypeFromExtension("png")};
Cursor cursor = context.getContentResolver()
.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, selectionMimeType, selectionArgs,
orderBy + " DESC");
if (cursor != null)
{
int idColumnIndex = imageCursor.getColumnIndex(MediaStore.Images.Media._ID);
imageCursor.moveToFirst();
int imageCount = imageCursor.getCount();
for (int i = 0; i < imageCount && i < totalCount; i++)
{
final long imageId = imageCursor.getLong(idColumnIndex);
Uri uriImage = Uri.withAppendedPath(uriExternal, "" + imageId);
GalleryData galleryImageData = new GalleryImageData(imageId, uriImage); // Custom class with id and Uri
galleryViewModelList.add(galleryImageData);
imageCursor.moveToNext();
}
imageCursor.close();
}
Why the images that i stored in the folder in Mediastore is not being returned by the above code when i reinstall my App. Is it by design or am i missing something?
These are the columns i am retrieving,
final String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID, MediaStore.Images.Media.MIME_TYPE };
final String orderBy = MediaStore.Images.Media.DATE_TAKEN; ```
For unclear reasons, Android 10 considers two app installations separated by time to be separate apps, no different than if those were two completely different apps.
As a result, once your app is uninstalled, it loses access to all files that it created... even if the same app is later reinstalled.
So, you need to treat files from a previous installation of your app the same way as you would treat files from completely unrelated apps: request READ_EXTERNAL_STORAGE to be able to query for them and read their contents. The "read their contents" part requires android:requestLegacyExternalStorage="true" on the <application> in the manifest.
Related
I got a MP4 File (MIME_TYPE = audio/x-m4a) which I can't find quering mediastore.
I don't know if it's a video or an audio file, so I try to query File table of mediastore (also checke video and audio table with same result).
The file is stored in Music/MP4TestFiles Folder on internal storage (not on an SD-Card, not stored in internal app storage).
Testing on Android 13.
Uri uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
String audiodir = "%MP4TestFiles%";
String[] projection = new String[] {
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns._ID};
String selection = MediaStore.Files.FileColumns.RELATIVE_PATH+ " like ? ";
String[] selectionArgs = new String[] {audiodir};
String sortOrder = MediaStore.Files.FileColumns.DATE_ADDED + " ASC";
Cursor cur = getContentResolver().query(uri, projection, selection, selectionArgs,sortOrder);
if (cur == null) {
Log.e(TAG, "mp4...no file found in Directory: "+audiodir);
} else if (!cur.moveToFirst()) {
Log.e(TAG, "mp4 Failed to move cursor to first row (no query results). 1");
} else {
Log.d(TAG, "mp4 files found")
}
Permission is set in Manifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage"/>
If I rename the file into m4b, I got a result.
Any suggestions what's going wrong?
Thanks,
GGK
I developed an app which saves photos inside the app folder (/android/data/[app-folder]/files/Report/[directory]) with some data saved into DESCRIPTION field, here is my code to save image:
ContentValues image = new ContentValues();
image.put(Images.Media.TITLE, directory);
image.put(Images.Media.DISPLAY_NAME, FOTOC_PREFIX_NAME + "_" + directory + "_" + data + ".jpg");
image.put(Images.Media.DESCRIPTION, "EXAMPLE DESC");
image.put(Images.Media.DATE_ADDED, System.currentTimeMillis());
image.put(Images.Media.MIME_TYPE, "image/jpg");
image.put(Images.Media.ORIENTATION, 0);
File parent = imagePath.getParentFile();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
image.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, parent.getAbsolutePath());
}
image.put(Images.ImageColumns.BUCKET_ID, parent.toString().toLowerCase().hashCode());
image.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, parent.getName().toLowerCase());
image.put(Images.Media.SIZE, imagePath.length());
image.put(Images.Media.DATA, imagePath.getAbsolutePath());
Uri result = getContentResolver().insert(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), image);
And then i read data with:
String whereCause = MediaStore.Images.Media.DISPLAY_NAME + " LIKE '%" + UsefullThings.getNameWithoutExtension(photofile.getName()) + "%'";
String[] projection = new String[] {MediaStore.Images.Media.DISPLAY_NAME,MediaStore.Images.Media.DESCRIPTION,MediaStore.Images.Media.TITLE,MediaStore.Images.Media._ID};
Cursor cursor = getContentResolver().query(MediaStore.Images.Media
.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), projection, whereCause, null, MediaStore.Images.Media._ID);
if (cursor.getCount() == 1) {
cursor.moveToFirst();
}
String descstring = "";
try {
descstring =cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DESCRIPTION));
}catch(Exception e){
....
}
where photofile is the image.
Everything is working fine, but if i reboot my tablet then the same query returns empty cursor.
Can anyone help me?
Thanks
app which saves photos inside the app folder (/android/data/[app-folder]/files/Report/[directory])
You mean /storage/emulated/0/Android/data/[app-folder]/files/Report/[directory] ?
That is impossible to begin with if you use the MediaStore.
That is an app specific directory and the MediaStore will not give you insert() uries for that location.
image.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, parent.getAbsolutePath());
You should put a relative path in .RELATIVE_PATH. Not a full absolute path as you do now. Also we cannot see what exactly you put in there. Please tell value of parent.getAbsolutePath().
I'm working on scanner app. My problem is while retrieve saved image from scoped storage in Android 10 and 11. Image is saved successfully but I am not able to retrieve it. When I get path of save image bitmap is null.
Here is the code to save the image in Android 11:
File dirDest = new File(Environment.DIRECTORY_PICTURES, context.getString(R.string.app_name));
Long date = System.currentTimeMillis();
String extension = ".jpg";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, date + extension);
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/" + extension);
contentValues.put(MediaStore.Images.Media.DATE_ADDED, date);
contentValues.put(MediaStore.Images.Media.DATE_MODIFIED, date);
contentValues.put(MediaStore.Images.Media.SIZE, bitmap.getByteCount());
contentValues.put(MediaStore.Images.Media.WIDTH, bitmap.getWidth());
contentValues.put(MediaStore.Images.Media.HEIGHT, bitmap.getHeight());
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, dirDest + File.separator);
Uri newImageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
FileOutputStream fos = (FileOutputStream) resolver.openOutputStream(Objects.requireNonNull(newImageUri));
callback.write(fos);
fos.flush();
fos.close();
return String.valueOf(dirDest) + date + extension;
Here is the code to retrieve the save image from shared storage in scoped storage in Android 11. I am getting issue in this part of code, bitmap is null. I am not getting the main point of the problem. In the end the problem is FileNotFoundException.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
String[] file = {MediaStore.Images.Media._ID,
MediaStore.Images.Media.RELATIVE_PATH,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.DATE_MODIFIED};
if (uri != null) {
cursor = context.getContentResolver().query(uri, file, null, null, null);
if (cursor != null) {
try {
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(file[0]);
File path = new File(uri.getPath());
bitmap = BitmapFactory.decodeFile(path.getAbsolutePath());
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
You can query them all with the help of SimpleStorage:
val images: List<MediaFile> = MediaStoreCompat.fromMediaType(applicationContext, MediaType.IMAGE)
images.forEach {
println(it.absolutePath)
}
Hello guys i am trying to get the absolute path of audio file in android api level 29 i.e, Q but the problem is that DATA is deprecated so how to resolve this problem and also using the absolute path (e.x : storage/sdcard/music.mp3) we can get the embedded picture to that audio but in content uri (content://) that we can get using the _ID Column by appending the id of the file.But if we are not able to to get the absolute path then how to play an audio file using content:// Uri ?
The previous codes that i am using is :
public ArrayList<Song> FolderFilesList(Context context, String FolderPath) {
//folder-exclude-sub-folders
ArrayList<Song> songs = new ArrayList<>();
String[] columns = {
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.TRACK,
MediaStore.Audio.Media.ARTIST_ID,
MediaStore.Audio.Media.DISPLAY_NAME,
};
String selection = MediaStore.Audio.Media.DATA + " LIKE ? AND " + MediaStore.Audio.Media.DATA + " NOT LIKE ? ";
String[] selectionArgs = new String[]{
"%" + FolderPath + "%",
"%" + FolderPath + "/%/%"
};
Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
columns(), selection, selectionArgs, null);
if (cursor.moveToFirst()) {
do {
Song song = new Song(
cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID)),
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE)),
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM)),
cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID)),
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)),
cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST_ID)),
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)),
cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.TRACK)),
cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION))
);
//File f = new File(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)));
// if (f.isFile() && f.exists()) {
songs.add(song);
//}
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songs;
}
You won't be able to get the absolute path now but you can get content uri and using that content uri means (content://) and use it to write file in your cache directory of your package folder using the below code :
String filePath = new File(resolver.openFileDescriptor(contentUri, "r");
And then use InputStream and OutputStream to store it in cache directory and use this filePath to as a File.
Use this line in application in the Manifest file
android:requestLegacyExternalStorage="true"
I am working on an app that list all the audio files from shared internal storage and removable SD card.
Now if the user wants to delete a particular file , it will be deleted from shared internal storage or removable SD card.
The issue I am facing is file.delete does not work , I have used mediastore to get all audio files.
These were the audio file paths i got from media store.
This is from internal shared storage.
/storage/emulated/0/Music/Guitar1.mp3
This is from removable micro SD card.
/storage/BBF8-A8D3/Guitar1.mp3
After getting these paths
File deleteFile = new File(s.getFilepath());
boolean delete = deleteFile.delete();
The delete gives false as delete file is not deleted.
Now I have tried this,
File deleteFile = new File(s.getFilepath());
if(deleteFile.exists()) {
boolean catchdelete = deleteFile.delete();}
Now after creating file from path , if condition fails as delete File does not exist.
So why newly created file does not exist(file is not a directory) does it require file input stream.
My main issue is to delete a file from storage through the app.
This is my method for retreiving audio file paths
public ArrayList<String> getAudiosPath(Activity activity, Context context) {
// Uri uri;
listOfAllAudios = new ArrayList<String>();
Cursor cursor;
final String[] columns = {MediaStore.Audio.Media.DATA, MediaStore.Audio.Media._ID};
final String orderBy = MediaStore.Audio.Media._ID;
//Stores all the audio from the gallery in Cursor
cursor = getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, columns, null,
null, orderBy);
//Total number of audios
int count = cursor.getCount();
//Create an array to store path to all the audios
String[] arrPath = new String[count];
for (int i = 0; i < count; i++) {
cursor.moveToPosition(i);
int dataColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
//Store the path of the audio
arrPath[i] = cursor.getString(dataColumnIndex);
Bitmap b = ((BitmapDrawable) ResourcesCompat.getDrawable(context.getResources(), R.drawable.headphone512, null)).getBitmap();
bitmap.add(b);
Log.i("PATH", arrPath[i]);
listOfAllAudios.add(arrPath[i]);
}
// count_paths=listOfAllAudios.size();
return listOfAllAudios;
}
Now i have applied Apache Commons IO File Utils
File deleteFile = new File(s.getFilepath());
// boolean delete = deleteFile.delete();
try {
FileUtils.forceDelete(FileUtils.getFile(s.getFilepath()));
}
catch (IOException e) {
e.printStackTrace();
}
This Apache Commons file utils does delete the file but issue is when opening the app again i am seeing the file path with file size 0 KB.
In Download Nav drawer
when in Nav drawer i access TA-1032-> Music -> Empty(No File present)
(there is no file which means file gets deleted)
But in Nav drawer i access Audio-> Unknown -> Music -> Guitar.mp3 (file present but file size is 0 and cant be played)
so this is some how getting the path of file.
Try using Apache Commons as a dependency and use their file API for the operation.
FileUtils.forceDelete(FileUtils.getFile(s.getFilepath()));
This piece might work.
Files.deleteIfExists(Paths.get("C:\\Users\\Mayank\\Desktop\\
445.mp3"));
Code snip will be helpful
File directory = new File("c:\\directoryname\\filename.txt");-- give your path where file is located.
try {
FileUtils.forceDelete(directory);
System.out.println("force delete file in java");
}
catch (IOException e) {
e.printStackTrace();
}
This worked for me , the code posted by #dammina deleted the file but still was accessible from media store so the other method will take care of it.
File deleteFile = new File(s.getFilepaths());
try {
FileUtils.forceDelete(FileUtils.getFile(s.getFilepaths()));
// adapterRecycler.notifyDataChanged();
adapterRecycler.notifyDataChanged(sectionHeaders);
}
catch (IOException e) {
e.printStackTrace();
}
deleteFileFromMediaStore(getContentResolver(), deleteFile);
method for deleting from media store because even after deleting file it is still accessible through media store.
public static int deleteFileFromMediaStore(final ContentResolver contentResolver, final File file) {
String canonicalPath;
try {
canonicalPath = file.getCanonicalPath();
} catch (IOException e) {
canonicalPath = file.getAbsolutePath();
}
// MediaStore.Files.FileColumns.DATA
final Uri uri = MediaStore.Files.getContentUri("external");
final int result = contentResolver.delete(uri,
MediaStore.Audio.Media.DATA + "=?", new String[]{canonicalPath});
if (result == 0) {
final String absolutePath = file.getAbsolutePath();
if (!absolutePath.equals(canonicalPath)) {
int deletedRow = contentResolver.delete(uri,
MediaStore.Audio.Media.DATA + "=?", new String[]{absolutePath});
return deletedRow;
}
} else return result;
return result;
}