I'm using the following code to get all audio files (actually their ids so I can play them later with MediaPlayer) from a directory on my android device:
ContentResolver cr = context.getContentResolver();
Uri audioUri = MediaStore.Audio.Media.getContentUriForPath(dir.getPath()); //dir is a File object representing the dir I'm looking in
Cursor audioCursor = cr.query(audioUri, null, null, null, null);
if (audioCursor != null && audioCursor.moveToFirst()) {
int idColumn = audioCursor.getColumnIndex(MediaStore.Audio.Media._ID);
List<Long> fileIds = new ArrayList<>();
do {
long id = audioCursor.getLong(idColumn);
fileIds.add(id);
} while (audioCursor.moveToNext());
return fileIds;
} else {
return null;
}
For some reason however, it returns an array with 2904 ids for any dir I give it. I'm guessing there are 2904 audio files on my device (when I use Uri audioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI it also returns 2904 ids). So it seems I'm using getContentUriForPath incorrectly, however the android reference doesn't give any information.
How should this work?
Some more info about what I'm trying to do (if it makes any difference in your answer): I'm still working on the code, trying out different things. Ultimately I want to play the files with MediaPlayer. I only need the audio files in that folder directly, ignoring any subfolders. They should also be ordered by filename.
You can't use getContentUriForPath with a folder, there isn't any content registered at this path. In this case getContentUriForPath return the standard EXTERNAL_CONTENT_URI, for this reason you get all the ids available.
This return all the files contained in a particular folder (e.g. /storage/emulated/0/Music/), ignoring any subfolders and ordered by filename:
Cursor audioCursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null,
MediaStore.Audio.Media.DATA + " LIKE ? AND " + MediaStore.Audio.Media.DATA + " NOT LIKE ?",
new String[]{path + "%", path + "%/%"},
MediaStore.Audio.Media.DISPLAY_NAME + " ASC");
if (audioCursor != null && audioCursor.moveToFirst()) {
int idColumn = audioCursor.getColumnIndex(MediaStore.Audio.Media._ID);
List<Long> fileIds = new ArrayList<>();
do {
long id = audioCursor.getLong(idColumn);
fileIds.add(id);
} while (audioCursor.moveToNext());
return fileIds;
} else {
return null;
}
Related
I'm using MediaStore.Images.Media.EXTERNAL_CONTENT_URI with a filter on MediaStore.MediaColumns.DATA and a Cursor to retrieve the images. I need to keep the image path where the images are stored, (which are in sub-folders) because I am using the image path to find the correct images to display them with their parent inventory item. I've tried many different ways with no success except using the MediaStore. This is working. The problem is; the MediaStore.MediaColumn.DATA is deprecated (sigh). So I need to refactor out the deprecated field use.
This is an inventory application where each inventory item has a "Source", "Order Number", "OrderItemNumber" plus other data. I stored the images on the sdCard in a folder I created under "downloads" called "InventoryImages". Under this folder are sub-folders (actual source name), (actual Order Number), (actual Order Item Number). Within the "OrderItemNumber" folder are several images, all for this specific combination.
The Inventory Item data is stored in a sqlite db. ((preloaded)(working on the maintenance pieces)). But I'm really trying to avoid storing the image (blob) in the db. But I am open to any and all suggestions for an alternative. I'd rather not have the images deleted if the application is uninstalled. I currently have 104 inventory items, each with average 4 images.
Again, the included fragment is working, I just need an alternative to the DATA column which the value is the image path.
Fragment:
public class ListViewFragment extends Fragment
implements IAdapterCallback
{
public ArrayList<ImageInfo> getAllMediaStoreImages()
{
ArrayList<ImageInfo> allImages = new ArrayList<>();
String inventoryDirectory;
if(Build.FINGERPRINT.contains("generic"))
inventoryDirectory = "Inventory"; // <-- use this for the emulator
else
inventoryDirectory = "InventoryImages"; // <-- use this for my phone
Uri uri =
MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String selection =
MediaStore.MediaColumns.DATA + " LIKE ? ";
String[] selectionArgs = new String[]{"%" +
inventoryDirectory + "%"};
Cursor cursor =
this.getActivity().getContentResolver().query(uri,
null, selection, selectionArgs, null);
String[] names = cursor.getColumnNames();
try {
if (cursor != null) {
while (cursor.moveToNext())
{
String filePath =
cursor.getString(cursor.getColumnIndex((MediaStore.MediaColumns.*DATA*)));
String fileName =
cursor.getString(cursor.getColumnIndex((MediaStore.MediaColumns.TITLE)));
int inventoryDirectoryLoc =
filePath.indexOf(inventoryDirectory) +
inventoryDirectory.length() + 1;
String matchPath =
filePath.substring(inventoryDirectoryLoc,
filePath.lastIndexOf('/'));
allImages.add(new
ImageInfo(filePath, fileName, matchPath));
}
}
return allImages;
}
catch (Exception x)
{
x.printStackTrace();
return null;
}
finally
{
if(cursor != null)
cursor.close();
}
}
This worked:
public ArrayList<File> getItemInventoryImages(String sourceSpecificPath)
{
String sourcePath = Environment.getExternalStorageDirectory().toString() + "/Download/" + sourceSpecificPath;
File file = new File(sourcePath);
if(!file.isDirectory()) {return null;}
if(!file.canRead() {return null;}
File[] files = file.listFiles();
return files;
}
Now I can feed the File array to the Adapter and use Glide to display them.
I just need the path of folders that contain at least one file that ends with eg. .mp4 or .jpg . I already checked this thread: Android: how to get all folders with photos? but it's too slow for my use case. Is there a faster way to do that?
So I finally found a solution which is way faster than the mentioned above. I got all directories with images within ~100-150ms (around 3k files). In the example below all image files stored either on the internal or external storage gets checked and the parent directory is added to the array list. I excluded .gif and .giff files.
This method also works with videos therefore a few steps are required:
change queryUri to MediaStore.Video.Media.EXTERNAL_CONTENT_URI
change projection to MediaStore.Video.Media.DATA
change 'image/%' in includeImages to 'video/%'
delete + excludeGif from selection
public static ArrayList<String> getImageDirectories(Context mContext) {
ArrayList<String> directories = new ArrayList<>();
ContentResolver contentResolver = mContext.getContentResolver();
Uri queryUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = new String[]{
MediaStore.Images.Media.DATA
};
String includeImages = MediaStore.Images.Media.MIME_TYPE + " LIKE 'image/%' ";
String excludeGif = " AND " + MediaStore.Images.Media.MIME_TYPE + " != 'image/gif' " + " AND " + MediaStore.Images.Media.MIME_TYPE + " != 'image/giff' ";
String selection = includeImages + excludeGif;
Cursor cursor = contentResolver.query(queryUri, projection, selection, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
String photoUri = cursor.getString(cursor.getColumnIndex(projection[0]));
if(!directories.contains(new File(photoUri).getParent())){
directories.add(new File(photoUri).getParent());
}
} while (cursor.moveToNext());
}
return directories;
}
We are trying to fetch the path of a file that is saved in SD Card, to pass on to a utility. However when we use the URI to open a cursor, only two columns are being fetched, the path (_data column) is missing. Please let me know how to fetch the path. The query works fine for any file that is in internal storage.
Scheme is "content".
Code snippet
cursor = context.getContentResolver().query(uri, null, null, null, null);
if (null != cursor && cursor.moveToFirst())
{
int testcolCount = cursor.getColumnCount();
for (int i = 0; i < testcolCount; i++)
{
String colName = cursor.getColumnName(i);
String colValue = cursor.getString(i);
System.out.println("" + i + ": " + colName + ":" + colValue + "\n");
}
//This prints only two columns, _display_name and _size
}
Don't try and get the path at all, with Android 10 and above file paths outside of your App's private directories are useless because you won't have permission to access them.
See https://developer.android.com/training/data-storage#scoped-storage
Use the contentResolver to get a FileDescriptor to pass on to the Utility
Note From https://developer.android.com/reference/android/provider/MediaStore.MediaColumns.html#DATA
This constant was deprecated in API level 29.
Apps may not have filesystem permissions to directly access this path. Instead of trying to open this path directly, apps should use ContentResolver#openFileDescriptor(Uri, String) to gain access.
try this code i am using this function to get real path from uri:
private String getRealPathFromURI(Context mContext, Uri contentURI) {
String result = null;
Cursor cursor = null;
try {
String[] proj = {MediaStore.Video.Media.DATA};
ContentResolver mContentResolver = mContext.getContentResolver();
String mime = mContentResolver.getType(contentURI);
cursor = mContentResolver.query(contentURI, proj, null, null, null);
if (cursor == null) {
return null;
} else {
cursor.moveToFirst();
int column_index = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
if (column_index > -1)
result = cursor.getString(column_index);
cursor.close();
}
} catch (Exception e) {
return null;
} finally {
if (cursor != null)
cursor.close();
}
return result;
}
it will return a string which is real path of your file.
hope it will help you.
The default File manager was the one causing this problem - not returning "DATA" column and returning Authority as "com.lenovo.FileBrowser.FileProvider". When I used another file manager, the Authority was "media". Also got the path column using "DATA" while running the ContenResolver. Thanks to all for taking the effort to help out.
I have an application that writes jpg image files to a directory structure in the app's external storage.
I was planning to use ContentResolver to scan through the directory structure so I can search both a specific folder or all folders. However, the ContentResolver query I wrote does not return any of the files written by the app (it does return other files in the phone though).
Here is the code:
// Create Content Resolver
ContentResolver resolver = getContentResolver();
// List of fields we want to include in the query
String[] projection = new String[] { BaseColumns._ID,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.SIZE };
// Run the query and return a crusor
cursor = resolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
// URI = Uniform Resource Identifier
projection, // which fields
null , // selection criteria
null, // selection arguments
MediaStore.MediaColumns.SIZE + " DESC");
if ((cursor != null) && (cursor.moveToFirst()))
{
do // loop through the items in the list of results, using the cursor
{
long id = cursor.getLong(0);
String name = cursor.getString(1);
long size = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE));
Log.i(TAG, "Image: id = " + id + " name = " + name
+ " size = " + size);
} while (cursor.moveToNext());
} else {
Log.w(TAG, "System media store is empty.");
}
I tried restarting the phone (so the ContentProvider will be updated), I tried on several different devices and emulators. Nothing helped.
What am I doing wrong?
While we are at it, could you please guide me how to filter the results by the directory name (e.g. show only the files in a specific sub-directory, or all the files under the app's external storage directory).
Thank you!
Yossi
I am writing an android app that allows me to update media files.
I have an AsyncTask that (among other things) scans the device for media files.
The code below is the method used to get all the media files and create a list of FileInfo (my class, holding basic meta data of files).
The problem is that the data received from the Cr().query is always the same and not bringing the latest updates.
I have looked at this similar issue but you can see that my code does the solution recommended there, yet I still get the old data rather than the refreshed.
Took me a while to figure out this is the problem, but I now see it clearly in the output of the log statement Log.d("scanLocalFiles", "" + fileInfos.size() + "|" + data + ":" + size );
that a file that I know that has been changed, still appears with its original size.
(I know it has been changed because I see the updated one on ES-FileExplorer...)
Any suggestion of what might be going on?
Thanks.
Code below:
private List<FileInfo> scanLocalFiles(Uri... uris)
{
List<FileInfo> fileInfos = new ArrayList<FileInfo>();
String[] projection = new String[]{
MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.MIME_TYPE
};
for (int u=0; u < uris.length; u++)
{
Uri uri = uris[u];
//Uri images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cur = Cr().query(uri,
projection, // Which columns to return
"", // Which rows to return (all rows)
null, // Selection arguments (none)
"" // Ordering
);
if(cur!=null && cur.moveToFirst())
{
try
{
int idColumn = cur.getColumnIndex(projection[0]);
int dataColumn = cur.getColumnIndex(projection[1]);
int sizeColumn = cur.getColumnIndex(projection[2]);
int mimeColumn = cur.getColumnIndex(projection[3]);
do
{
Uri id = Uri.withAppendedPath(uri, cur.getString(idColumn));
String data = cur.getString(dataColumn);
long size = cur.getLong(sizeColumn);
String mime = cur.getString(mimeColumn);
Log.d("scanLocalFiles", "" + fileInfos.size() + "|" + data + ":" + size );
fileInfos.add(new FileInfo(id, data, size, mime));
} while (cur.moveToNext());
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
cur.close();
}
}
}
return fileInfos;
}