Android - Convert URI to file path on lollipop - android

I'm currently attempting to make a media player for audio. I'm currently running lollipop. I ran into a problem setting the dataSource for the media player. First, here's how I set the dataSource:
public void playSong() {
player.reset();
Song selSong = songs.get(songPos);
long currSong = selSong.getId();
//Get Uri of song
Uri trackUri = ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, currSong);
try {
//Tell the player the song to play
player.setDataSource(getApplicationContext(), trackUri);
} catch (Exception e) {
Log.e("MUSIC SERVICE", "Error setting data source", e);
Log.d("URI Path", trackUri.toString());
}
//Will prepare the song and call onPrepare of player
player.prepareAsync();
}
And the Uri comes out to this:
content://media/external/audio/media/22
I did some research, and from my understanding, after Android 4.1, you can no longer use a URI for the dataSource. When I run this app with the code above, I'll get this error:
E/MediaPlayer﹕ Unable to create media player
E/MUSIC SERVICE﹕ Error setting data source
java.io.IOException: setDataSource failed.: status=0x80000000
at android.media.MediaPlayer.nativeSetDataSource(Native Method)
So now I need to convert the URI to a file path and provide that as the dataSource. And, after more research, it appears kitkat changed the way URI's are provided so its difficult to get the file path from the URI. However, I'm not sure if this change persisted into Android Lollipop 5.0.2.
Essentially, I have the URI of a song, but I need to provide something other than a URI to a dataSource. Is there any way that I can convert the URI on Lollipop, and, if not, how can I provide the dataSource only knowing the song's id? Thanks.

Lollipop decided to take another course with getting files from the system. (Some say it is from KitKat, but I haven't encountered it yet on KitKat). The code below is to get the filepath on lollipop
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isMediaDocument(uri))
{
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("audio".equals(type))
{
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
String filePath = getDataColumn(context, contentUri, selection, selectionArgs);
}
isMediaDocument:
public static boolean isMediaDocument(Uri uri)
{
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
getDataColumn:
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs)
{
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst())
{
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
If you still have problems, this is the full answer that checks for images, audio, video, files, etc.

Related

in Android 11 i'm not able to get file path from document optio [duplicate]

This question already has answers here:
Android Kotlin: Getting a FileNotFoundException with filename chosen from file picker?
(5 answers)
Android - Get real path of a .txt file selected from the file explorer
(1 answer)
Closed 1 year ago.
check the image for more understanding and thanks in advance. In this screenshot all options are good but I don't want to display the document option here but also need a document to be picked. if there is not any way to hide the option then need help to get the path of file
if (isMediaDocument(uri)) {
Log.e("URI", "" + uri);
final String docId = DocumentsContract.getDocumentId(uri);
Log.e("docId", "" + docId);
final String[] split = docId.split(":");
final String type = split[0];
Log.e("TYPE", "" + type);
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
} /*else if ("document".equals(type)) {
contentUri = uri;
}*/
selection = "_id=?";
selectionArgs = new String[]{split[1]};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
if (isGoogleDriveUri(uri)) {
return getDriveFilePath(context, uri);
}
if (isWhatsAppFile(uri)) {
return getFilePathForWhatsApp(context, uri);
}
if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
}
You cannot grey that option out.
And even if you would manage to implement your getRealPathForUri() to deliver a file system path your app would not have read and write access on an Android 11+ device.
So what you want makes no sense.
Use the uri directly.
Every body does now adays.

Alternative to MediaStore.Video.Media.EXTERNAL_CONTENT_URI for multiple videos

I'm basically trying to view all the photos and videos in a given folder from my Camera app, when the preview is clicked. So basically the user will view the latest photo while having the ability to swipe to view other photos that were previously taken. I have achieved this for photos, but am unable to do so for videos. There is only one URI related to videos in MediaStore class called MediaStore.Video.Media.EXTERNAL_CONTENT_URI that displays a given video (no way to access other photos/videos) unlike MediaStore.Images.Media.EXTERNAL_CONTENT_URI which is used to open the gallery while having access to multiple photos. Is there any other way I can show all the videos in a given folder while focusing on the latest video?
Code to open gallery
public void openGallery() {
String mediaId = "";
String[] projection = new String[] {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME
};
final String fileName = config.getLatestMediaFile().getName();
Uri mediaUri;
if(videoCapturer.isLatestMediaVideo()){
mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else {
mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
Log.i(TAG, "MediaURI: " + mediaUri.getPath());
Cursor cursor = getContentResolver().query(
mediaUri, projection, null, null, null);
if(cursor != null){
while (cursor.moveToNext()) {
String name = cursor.getString((cursor.getColumnIndex(MediaStore.Images.ImageColumns.DISPLAY_NAME)));
if(name.equals(fileName)){
mediaId = cursor.getString((cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)));
break;
}
}
cursor.close();
}
if(!mediaId.equals("")){
mediaUri = mediaUri.buildUpon()
.authority("media")
.appendPath(mediaId)
.build();
}
Log.d("TagInfo","Uri: "+mediaUri);
Intent intent = new Intent(Intent.ACTION_VIEW, mediaUri);
startActivity(intent);
}

Proper way to get file path when it's picked using ACTION_GET_CONTENT

I have a file picker implemented in my app like this:
Intent intent = new Intent();
intent.setType("*/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Title"), FILE_PICK);
It's a known issue that you can't easily get the actual file location this way because Intent will return some weird Uri that you can't really use to create a File object.
I'm using this method to get the actual File path:
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
*
* #param context The context.
* #param uri The Uri to query.
* #author paulburke
*/
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
This works for some files, and for some not. Right now I noticed that it doesn't work when I pick a file from "Downloads" folder. It skips all if statements above and returns null.
What would be the correct way, that would be more reliable to get the actual selected File path?
I have a file picker implemented in my app like this
That is not a "file picker". It is a content picker.
I need a way to be able to pick any file on Android device so that I can upload it to my backend.
Um, well, that depends a fair bit on how literal you are in that sentence.
ACTION_GET_CONTENT is not a problem in general. However, what you get are not necessarily files. However, you can still upload them, assuming that whatever you are using for the uploading allows you to upload from an InputStream. If so, use openInputStream() on a ContentResolver, passing in the Uri, to get the InputStream to use.
If you really need it to be actual files, you can implement a file-picker UI directly in your app. There are libraries for this. However, you will not be able to access all files — in particular, you cannot use this to upload files from removable storage on Android 4.4+.

How to refresh Android's MediaStore upon photo deletion

Question: how to make the media store to refresh its entry of a DELETED file?
After deleting a photo in code from the external storage, I still see a slot for the deleted photo in the gallery - blank photo.
It seems that the gallery reflects the media store and the deleted photo is found in the media store until the phone is restarted or generally - until the media is rescanned.
Trying to scan the deleted file did not help scanning deleted files (works just for new or existing files): MediaScannerConnection.scanFile(Application.get(), new String[]{file.getPath()}, null, null) (I tried scanning the parent folder as well).
Also tried ACTION_MEDIA_SCANNER_SCAN_FILE to no avail. Example: Application.get().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)))
Sending a broadcast receiver to rescan the entire external storage (thus refreshing the media store)did the trick: Application.get().sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(Environment.getExternalStorageDirectory())))
BUT, it seems that Android, as of 4.4, throws a security exception when trying to manually send the ACTION_MEDIA_MOUNTED system broadcast. See #CommonsWare's post: http://commonsware.com/blog/2013/11/06/android-4p4-permission-regressions.html
So, I'm stuck with no solution for refreshing the media store upon file(/photo/video/etc.) deletion.
I found the following that works for me in 4.4 on a Nexus 10.
// request scan
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, SELECT_PICTURE);
Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
scanIntent.setData(Uri.fromFile(refreshFile));
sendBroadcast(scanIntent);
"refreshFile" is the file I deleted that I get from my String "fPath" and then convert it to a file.
String filePath = fPath;
File refreshFile = new File(filePath);
I had the same issue. I wrote the following code and it worked on all versions from lollipop to oreo. I also called the mediastore.scanfile() method to ensure that MediaStore is updated. Adding the code below - you might not want to use the "delete()" method in future as the scanfile() might be comprehensive. But, if you want to support older phones then delete() would probably be safer.
// fileID == MediaStore.Images.Media._ID; for the file when you get the file from the content
// resolver
public static boolean deleteCREntryForFilePath(Context context, String filePath, long fileID) {
boolean fDeleted = false;
ContentResolver cr = context.getContentResolver();
int rowsDeleted = 0;
Uri imageURI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String deleteStr = MediaStore.Images.Media._ID + "=" + fileID;
MediaScannerConnection.scanFile(context, new String[]{filePath}, null, null);
// Remove entry from database
rowsDeleted = context.getContentResolver().delete(
imageURI, deleteStr, null);
if (rowsDeleted > 0)
fDeleted = true;
return(fDeleted);
}
Here is the code to get the file-id (name of the function is getfileId()) . It works for different file-types. You cannot compile the code as it is because it uses an internal object-type but you should be easily able to convert this for generic use.
public static String[] getCombinedEntityColumns(Constants.DELASHARE_OBJECT_TYPES objType) {
String[] entityColumns = new String[5];
switch (objType) {
case DELASHARE_OBJECT_PICTURE:
case DELASHARE_OBJECT_MUSIC:
case DELASHARE_OBJECT_VIDEO: {
entityColumns[0] = MediaStore.Images.Media.DISPLAY_NAME;
entityColumns[1] = MediaStore.Images.Media.DATA;
entityColumns[2] = MediaStore.Images.Media._ID;
entityColumns[3] = MediaStore.Images.Media.DATE_ADDED;
//entityColumns[3] = MediaStore.Images.Media.DATE_TAKEN;
entityColumns[4] = MediaStore.Images.Media.SIZE;
break;
}
case DELASHARE_OBJECT_APK:
case DELASHARE_OBJECT_DOCUMENT:
case DELASHARE_OBJECT_DOWNLOAD:
case DELASHARE_OBJECT_SEARCH_RESULTS:
default: {
entityColumns[0] = MediaStore.Files.FileColumns.DISPLAY_NAME;
entityColumns[1] = MediaStore.Files.FileColumns.DATA;
entityColumns[2] = MediaStore.Files.FileColumns._ID;
entityColumns[3] = MediaStore.Files.FileColumns.DATE_MODIFIED;
entityColumns[4] = MediaStore.Files.FileColumns.SIZE;
break;
}
}
return (entityColumns);
}
public static Uri getCategoryUri(Constants.DELASHARE_OBJECT_TYPES categoryObjType) {
Uri objUri = null;
switch(categoryObjType) {
case DELASHARE_OBJECT_PICTURE:
objUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
break;
case DELASHARE_OBJECT_VIDEO:
objUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
break;
case DELASHARE_OBJECT_MUSIC:
objUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
break;
case DELASHARE_OBJECT_DOWNLOAD: {
File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
objUri = Uri.fromFile( downloadDir);
//objUri = MediaStore.Files.getContentUri("external");
break;
}
case DELASHARE_OBJECT_APK:
case DELASHARE_OBJECT_DOCUMENT:
case DELASHARE_OBJECT_SEARCH_RESULTS:
default:
objUri = MediaStore.Files.getContentUri("external");
break;
}
return(objUri);
}
public static long getFileId(Context context, String dirPath, String filePath, String fileName, Constants.DELASHARE_OBJECT_TYPES objType) {
boolean fIDFound = false;
long id = 0;
if (!fIDFound) {
String sortOrder = null;
String[] entityColumns = getCombinedEntityColumns(objType);
Uri categoryUri = getCategoryUri(objType);
String selection = null;
String[] selectionArgs = new String[]{Constants.DELA_PERCENT_STR + dirPath};
ContentResolver cr = context.getContentResolver();
Cursor cursor = null;
switch (objType) {
case DELASHARE_OBJECT_PICTURE:
selection = MediaStore.Images.Media.DATA + " LIKE ?";
break;
case DELASHARE_OBJECT_VIDEO:
selection = MediaStore.Video.Media.DATA + " LIKE ?";
break;
case DELASHARE_OBJECT_DOCUMENT:
default:
selection = MediaStore.Files.FileColumns.DATA + " LIKE ?";
break;
}
cursor = cr.query(
categoryUri,
entityColumns,
selection,
selectionArgs,
sortOrder);
if (cursor != null && cursor.moveToFirst()) {
id = cursor.getLong(cursor.getColumnIndex(entityColumns[2]));
if (id != 0) {
fIDFound = true;
}
}
if (cursor != null) {
cursor.close();
cursor = null;
}
}
return(id);
}
I had the same question as you now that the sendBroadcast approach is disallowed in 4.4 and found a good solution here using the Media Store content provider: https://stackoverflow.com/a/20780472/1060805
I tested it out on Android 4.4 and it works nicely. I think it is a solid approach.
Try
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
Add this to your manifest:
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file" />
</intent-filter>

Retrieve file name of content from other apps

I have registered my app to receive files (of any type, not just images) from other apps following this post.
I have implemented the solution that was answered but I cannot find a way to retrieve the "file name" of the data stream.
As an example from an Uri like:
content://downloads/all_downloads/5
I can get the stream out but I don't know anything about the name of the original file generating it.
Is there a way to retrieve it?
In MOST cases this will solve your problem:
Uri intentData = intent.getData();
if (intentData != null) {
String filePath;
if("content".equals(intent.getScheme()))
{
filePath = getFilePathFromContentUri(intentData);
}
else
{
filePath = intentData.getPath();
}
}
private String getFilePathFromContentUri(Uri selectedUri) {
String filePath;
String[] filePathColumn = {MediaColumns.DATA};
Cursor cursor = getContentResolver().query(selectedUri, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
Is there a way to retrieve it?
Generally, no, because there may not be a name, in part because there may not be a file. You may be able to get an InputStream on the contents, but that does not mean that there is a file behind the InputStream.
There may be some specific hacks for some specific providers (e.g., MediaStore) to try to determine the file name associated with some data Uri, though such hacks may not be reliable.
onCreate()
Intent intent1 = getIntent();
String action = intent1.getAction();
String type = intent1.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
this.handleSend(intent1);
}
void handleSend(Intent intent) {
try {
Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
imageShare.setImageURI(imageUri);
} catch (Exception e) {
e.printStackTrace();
}
}

Categories

Resources