How to get the real path with ACTION_OPEN_DOCUMENT_TREE Intent? - android

My app downloads and unzips a file in a specific folder:
output = new FileOutputStream(realpath, true);
output.write(buffer, 0, bytesRead);
ZipFile zipFile = new ZipFile(realpath);
With the new introduced ACTION_OPEN_DOCUMENT_TREE Intent, I would like to offer the user to choose that folder.
When testing the values received in my onActivityResult, I get a Path like /tree/primary:mynewfolder, which is not the physical real path like /sdcard/mynewfolder.
Uri treeUri = data.getData();
String sPath = treeUri.getPath();
Log.v("Path from Tree ", sPath);
My unzip method need the real path:
ZipFile zipFile = new ZipFile(realpath);
How do I get the real path like /sdcard/mynewfolder from the provided URI in Lollipop (API 21 & 22)?

Process of getting real Path from URI is same as before, but with a little addition:
Uri uri = data.getData();
Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
DocumentsContract.getTreeDocumentId(uri));
String path = getPath(this, docUri);
The gist of getPath() method with intermediate methods can be found here: getPath()

Apparently I need reputation to Comment or edit improves Minute V answer. since I guess it's still relevant for people who don't want external libraries or have retrocompatability.
The path you get from just the Uri should be relative to the Environment.getExternalStorageDirectory().getPath(). from what I gather it will have some ":" to split the info on the link.
You just have to split the link and take the last value in the array to get the relative path. Then you just use Environment.getExternalStorageDirectory().getPath()and add a "/" and then add your path.
Here is the code I used:
Uri uri = resultData.getData();
String [] pathsections = resultData.getData().getPath().split(":");
path = Environment.getExternalStorageDirectory().getPath() + "/" + pathsections[pathsections.length-1];
As you can see, there should be no need to do all does if's and Environment.getExternalStorageDirectory().getPath() should get you the path on a physical device too so you won't have to break your head around hard coding the path.

No need checking android version..
public static string GetRealPath(DocumentFile treeUri)
{
if (treeUri == null)
return "";
var path1 = treeUri.Uri.Path;
if (path1.StartsWith("/tree/"))
{
var path2 = path1.Remove(0, "/tree/".Length);
if (path2.StartsWith("primary:"))
{
var primary = path2.Remove(0, "primary:".Length);
if (primary.Contains(':'))
{
var storeName = "/storage/emulated/0/";
var last = path2.Split(':').LastOrDefault();
var realPath = storeName + last;
return realPath;
}
}
else
{
if (path2.Contains(':'))
{
var path3 = path2.Split(':').FirstOrDefault();
var storeName = path3;
var last = path2.Split(':').LastOrDefault();
var realPath = "/" + storeName + "/" + last;
return realPath;
}
}
}
return path1;
}

Related

opening an external "file explorer" app: how to get absolute path from a uri pointing to a folder

In my app, the user can choose where the created files (text files) are created.
This part is working fine.
But now, I want to open an external "file explorer" app, pointing directly to the chosen folder.
The "file explorer " apps I know accept an absolute path as input (like /storage/emulated/0/Documents/test_folder)
When the user chooses a folder (with Intent.ACTION_OPEN_DOCUMENT_TREE), I get a content uri (like content://com.android.externalstorage.documents/tree/home%3Atest_folder)
Another example with an external sd card:
uri: content://com.android.externalstorage.documents/tree/3877-DB74%3ADocuments%2Ftest_folder
expected path: /storage/3877-DB74/Documents/test_folder
The uri points to a folder, not a file, so I can't use something like openInputStream
I have tried :
File f = new File(uri.getPath());
String path = f.getAbsolutePath();
but it gives: /tree/home:test_folder or /tree/3877-DB74:Documents/test_folder if on sd card
How can I get the real absolute path?
The code I use to call a file explorer:
Intent intent = new Intent(Intent.ACTION_VIEW);
String path = getExternalFilesDir(null).getAbsolutePath();
intent.setDataAndType(Uri.parse(path), "resource/folder");
if (intent.resolveActivityInfo(getPackageManager(), 0) != null)
{
startActivity(intent);
}
so basically you want to get file path from uri
you give try with this code
https://gist.github.com/pratikbutani/eb56f6f9f7013e31d8bfea9effbd4251
I have tried the suggested code (see above).
Unfortunately, I got an exception:
Caused by: java.lang.UnsupportedOperationException: Unsupported Uri content://com.android.externalstorage.documents/tree/home%3Atest_folder
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:167)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
at android.content.ContentProviderProxy.query(ContentProviderNative.java:418)
at android.content.ContentResolver.query(ContentResolver.java:760)
at android.content.ContentResolver.query(ContentResolver.java:710)
at android.content.ContentResolver.query(ContentResolver.java:668)
at ....UriUtils.getDataColumn(UriUtils.java:278)
Here is a copy of the code:
private static String getDataColumn(Context context, Uri uri)
{
Cursor cursor = null;
final String column = "_data";
final String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection,
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
I finally wrote my own method to get the absolute path for a folder from a Uri.
It is surely not fully generic, but it meets my need.
if it can help someone, here is my code:
Note: VOLUME_MAP is a map containing all mounted external volumes
/**************************************************************************/
public static String getRealPathFromContentUri(final Uri uri)
{
if (!isExternalStorageDocument(uri))
{
return null;
}
List<String> segs = uri.getPathSegments();
if (!"tree".equalsIgnoreCase(segs.get(0)))
{
return null;
}
String path = uri.getLastPathSegment();
final String[] split = path.split(":");
final String volumeId = split[0];
String userPath = "";
if (split.length > 1)
{
userPath = "/" + split[1];
}
if ("primary".equalsIgnoreCase(volumeId))
{
return Environment.getExternalStorageDirectory().getAbsolutePath() + userPath;
}
if ("home".equalsIgnoreCase(volumeId))
{
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + userPath;
}
// look for real volumeId
final String volumeName = VOLUME_MAP.get(volumeId);
if (volumeName == null)
{
return null;
}
path = "/storage";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
{
path = Environment.getStorageDirectory().getAbsolutePath();
}
return path + "/" + volumeId + userPath;
}
Thanks to all contributors on this topic.

Xamarin Forms Android 10 and higher find video by filename in public external storage and get path

im developing a Xamarin Forms App where the User can take Videos and save them to the public external storage. Im currently saving the filename of the created video.
My way of saving the video:
private readonly string DirectoryName = "KiloFürKilo";
public async Task<string> CaptureVideoAsync()
{
var photo = await MediaPicker.CaptureVideoAsync();
await using var stream = await photo.OpenReadAsync();
await using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
var filename = "KforK" + DateTime.Now + ".mp4";
SaveVideoFromByte(memoryStream.ToArray(), filename);
return filename;
}
private async void SaveVideoFromByte(byte[] imageByte, string filename)
{
var context = CrossCurrentActivity.Current.AppContext;
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
//Android 10+
if (Android.OS.Build.VERSION.SdkInt > Android.OS.BuildVersionCodes.P)
{
using var resolver = context.ContentResolver;
var contentValues = new ContentValues();
contentValues.Put(MediaStore.IMediaColumns.DisplayName, filename);
contentValues.Put(MediaStore.IMediaColumns.MimeType, "video/mp4");
contentValues.Put(MediaStore.IMediaColumns.RelativePath, "DCIM/" + DirectoryName);
var uri = resolver.Insert(MediaStore.Video.Media.ExternalContentUri, contentValues);
using var stream = resolver.OpenOutputStream(uri);
await stream.WriteAsync(imageByte);
stream.Close();
mediaScanIntent.SetData(uri);
}
else
{
var rootPath =
Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryMovies);
var storagePath = Path.Combine(rootPath.ToString(), DirectoryName);
if (!File.Exists(storagePath))
{
Directory.CreateDirectory(storagePath);
}
string path = Path.Combine(storagePath.ToString(), filename);
File.WriteAllBytes(path, imageByte);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new Java.IO.File(path)));
}
context.SendBroadcast(mediaScanIntent);
}
I now want to be able to open and play the video, without the user need to pick it from the gallery.
How can i find a video in the external public storage by a filename and retrieve the path to it?
I think i have to use the MediaStore but could not figure out how.
Thanks to anyone who can help me with this problem:)
MediaStore insert() gave you an uri where you wrote the file.
To read/play the file you can use the same uri.
Further your relative path was "DCIM/"+DirectoryName;
And if you need a path then complete path is
"/storage/emulated/0/DCIM/"+DirectoryName+"/"+filename.

Could not find a part of the path "/content:/com.android.externalstorage.documents/document/6759-130B%3ANew%20Text%20Document.txt"

I am using FilePicker-Plugin-for-Xamarin-and-Windows and got this error while accessing external storage:
Could not find a part of the path "/content:/com.android.externalstorage.documents/document/6759-130B%3ANew%20Text%20Document.txt".
Note:I have both read and write permissions And also I ask user at runtime.
When using this path problem gone:
storage/6759-130B%3ANew%20Text%20Document.txt
This is very known old bug in picker as officially mentioned.
For me it looks like I have to convert content:// into file path.
More info
I remembered the thing, You need to get the absolute path from your uri. Usually, content:// path is returned from download folder or any drive path, you need to get actual path for it. You can try this.
Its an device specific code for Android, Inject it with dependency service.
I faced above issue in Native android & solved that as follows in Java, below is the converted code for the same in c#
private string GetRealPathFromURI(Uri contentURI)
{
ICursor cursor = ContentResolver.Query(contentURI, null, null, null, null);
cursor.MoveToFirst();
string documentId = cursor.GetString(0);
documentId = documentId.Split(':')[1];
cursor.Close();
cursor = ContentResolver.Query(
Android.Provider.MediaStore.Images.Media.ExternalContentUri,
null, MediaStore.Images.Media.InterfaceConsts.Id + " = ? ", new [] { documentId }, null);
cursor.MoveToFirst();
string path = cursor.GetString(cursor.GetColumnIndex(MediaStore.Images.Media.InterfaceConsts.Data));
cursor.Close();
return path;
}
EDIT -
Try this
FileData fileData = await CrossFilePicker.Current.PickFile();
string filePath;
if (fileData != null)
{
await Task.Run(() => {
filePath = DependencyService.Get<IImageUtilities>().SaveFileFromStream(new MemoryStream(fileData.DataArray), fileData.FileName));
});
Code for Xamarin.Android
public string SaveFileFromStream((System.IO.Stream imageStream, string filename)
{
string name = filename;
string filePath = null;
try
{
byte[] imageData = ((MemoryStream)imageStream).ToArray();
IFolder folder = FileSystem.Current.GetFolderFromPathAsync(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).ToString()).Result;
IFile file = folder.CreateFileAsync(name, CreationCollisionOption.GenerateUniqueName).Result;
filePath = System.IO.Path.Combine(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).ToString(), file.Name);
System.IO.Stream outputStream = file.OpenAsync(PCLStorage.FileAccess.ReadAndWrite).Result;
outputStream.Write(imageData, 0, imageData.Length);
outputStream.Flush();
outputStream.Close();
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
return filePath;
}
Later on, the new path of the file is used everywhere needed accordingly.
This is what I actually do in my project, whichever file is selected by the user, it is copied to Pictures Dir in internal storage & returns the path.
We deal with the ImageStream to make a copy of the original document.
The idea to make a duplicate copy is that we uses the copy for uploading purpose as the user may delete the original document selected. So after pushing the document to the server, we delete the copied file as well. So as we deal with the stream we don't face any issue with Content://.
Hope this maybe helpful.

How to open a file without knowing its extension but knowing full name?

Intent tostart = new Intent(Intent.ACTION_VIEW);
tostart.setDataAndType(Uri.parse(video_path+".***"), "video/*");
startActivity(tostart);
Let's say I have a file path
/mnt/sdcard/video/my_birthday_moovie001
'my_birthday_moovie001' can be either .mkv, .mpg or .mkv. I've tried to add ".***" to the file path but I still can't open the file.
Well i read the comments you have stored your path in db without extensions there are many extensions that exists so android cant automatically pick the extension you have to create some way to detect extension.
following is a robust way that is best match in your case but not recommended in proper cases where extensions are known
public String chk_path(String filePath)
{
//create array of extensions
String[] ext=new String[]{".mkv",".mpg"}; //You can add more as you require
//Iterate through array and check your path which extension with your path exists
String path=null;
for(int i=0;i<ext.Length;i++)
{
File file = new File(filePath+ext[i]);
if(file.exists())
{
//if it exists then combine the extension
path=filePath+ext[i];
break;
}
}
return path;
}
now to play a song in your code
if(chk_path(video_path)!=null)
{
Intent tostart = new Intent(Intent.ACTION_VIEW);
tostart.setDataAndType(Uri.parse(video_path), "video/*");
startActivity(tostart);
}
else
//tell user that although the path in database but file on this path do not exists
Well as I put on comments
You could compare if the path matches with any filename(it doesn't contains the extension) and then if it does you got it.
You can simply do this :
Get the directory path
File extStore = Environment.getExternalStorageDirectory();
Set the file name my_birthday_moovie001 on my example I put unnamed but change it as your like
String NameOfFile = "unnamed";
Add the videos, I put it Downloads but you can change it
String PathWithFolder = extStore + "/Download/";
Create a method that lists all the files from your path
private List<String> getListFiles(File parentDir) {
ArrayList<String> inFiles = new ArrayList<String>();
File[] files = parentDir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
inFiles.addAll(getListFiles(file));
} else {
String AbsolutePath = file.getAbsolutePath();
//Get the file name ex : unnamed.jpg
String nameofFile = AbsolutePath.substring(AbsolutePath.lastIndexOf("/") + 1, AbsolutePath.length());
//Remove the .jpg --> Output unnamed
String fileNameWithoutExtension = nameofFile.substring(0, nameofFile.lastIndexOf('.'));
//Add each file
inFiles.add(fileNameWithoutExtension);
}
}
return inFiles;
}
You got the names of the files doing this
List<String> files = getListFiles(new File(PathWithFolder));
Simply add a for that looks for a match of your file
for (int i = 0; i<=files.size()-1; i++){
if(PathWithFolder.equals(files.get(i))) {
Toast.makeText(MainActivity.this, "You got it!", Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(MainActivity.this, "You don't.", Toast.LENGTH_SHORT).show();
}
}
If you want to get the path as well and do what #Zain Ul Abidin proposed and compare it on getListFiles() method add this :
String fileExtension = nameofFile.substring(nameofFile.lastIndexOf("."));
Hope it helps.
From the other question :
Consider DirectoryScanner from Apache Ant:
DirectoryScanner scanner = new DirectoryScanner();
scanner.setIncludes(new String[]{"**/*.java"});
scanner.setBasedir("C:/Temp");
scanner.setCaseSensitive(false);
scanner.scan();
String[] files = scanner.getIncludedFiles();
You'll need to reference ant.jar (~ 1.3 MB for ant 1.7.1).
And then, run on files array and check
if files[i].include(yourfile)
yourfile= files[i]
You may try in this way , first getting the name of file and extension then finally compare and implement. like this :
Example file name is 04chamelon and extension is .png:
File f = new File("/mnt/storage/sdcard/Pictures/04chameleon");
File yourDir = new File("/mnt/storage/sdcard/Pictures");
nametwo = f.getName();
for (File fa : yourDir.listFiles()) {
if (fa.isFile())
fa.getName();
String path = fa.getName(); // getting name and extension
filextension = path.substring(path.lastIndexOf(".") + 1); // seperating extension
name1 = fa.getName();
int pos = name1.lastIndexOf(".");
if (pos > 0) {
name1 = name1.substring(0, pos);
}
}
if (name1.equals(nametwo)) {
Intent tostart = new Intent(Intent.ACTION_VIEW);
tostart.setDataAndType(Uri.parse(f + "." + filextension), "image/*");
//tostart.setDataAndType(Uri.parse(f + "." + filextension), "video/*");
startActivity(tostart);
}
With the latest ContentResolver, you can easily make this work using the contentResolver.getType(uri) function which detects the filetype.
private fun getIntentForFile(intent: Intent, filePath: String, context: Context): Intent {
val uri = FileProvider.getUriForFile(
context,
context.applicationContext.packageName + ".fileprovider",
File(filePath)
)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setDataAndType(uri, context.contentResolver.getType(uri))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent
}

Making Android filepaths uniform

I have three segments of code: one that sets the original filepath of the file, one that is used in renaming the file, and one that is used to match the file so that the file (audio recording) can be played.
My problem is that, to the best of my knowledge & what I have been able to find out online, I need "file://" before the rest of the filepath when I am renaming it...otherwise the MediaPlayer throws up exceptions when I try to do the playback. After much searching, I have not come up with a good way to make them uniform so that the "matcher" code can work on all the files. My best guess is that it would be ideal if I could find a way to not have to use "file://" before the rest of the filepath.
1) Code that sets original filepath:
public void setFileNameAndPath(){
int count = 0;
File f;
do{
count++;
mFileName = getString(R.string.default_file_name)
+ " #" + (mDatabase.getCount() + count) + ".mp4";
mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
mFilePath += "/SoundRecorder/" + mFileName;
f = new File(mFilePath);
}while (f.exists() && !f.isDirectory());
}
2) Renaming the filepath:
public void rename(int position, String name) {
//rename a file
String mFilePath = "file://" + Environment.getExternalStorageDirectory().getAbsolutePath();
mFilePath += "/SoundRecorder/" + name;
File f = new File(mFilePath);
if (f.exists() && !f.isDirectory()) {
//file name is not unique, cannot rename file.
Toast.makeText(mContext,
String.format(mContext.getString(R.string.toast_file_exists), name),
Toast.LENGTH_SHORT).show();
} else {
//file name is unique, rename file
File oldFilePath = new File(getItem(position).getFilePath());
oldFilePath.renameTo(f);
mDatabase.renameItem(getItem(position), name);
notifyItemChanged(position);
}
}
3) Matching the file:
Intent iin = getIntent();
Bundle b = iin.getExtras();
newString = (String) b.get("filename");
mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
mFilePath += "/SoundRecorder/" + newString;
I think file:// is the URI of the file and it's useful for example in a mediaplayer where the resource can exists on local storage (file://) or over internet (http://)
To "convert" string to URI use
Uri uri = Uri.parse("http://www.google.com");
And to "convert" URI to file use
File file = new File(uri.getPath());

Categories

Resources