In my application of image gallery I use media content provider image to inflate recycler view . On long press on an image, I give user option to rename that image file. So I have complete file path (Ex:- /storage/sdcard1/DCIM/100ANDRO/ak.jpg ) for each image in recycler view. Then I want to rename that file.
Now the issue is that as the file path provided is that of External SD Card, and for Android 5 & up, SAF(Storage Access Framework) is required to write a file.
So generally we use this code for renaming a file using SAF:-
public void onActivityResult(int requestCode, int resultCode, Intent resultData){
if (resultCode == RESULT_OK) {
Uri treeUri = resultData.getData();
getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
DocumentFile newFIle = pickedDir.createFile("text/plain","MyFile")
// or rename as
pickedDir.renameTo("fdtd.jpg");
} else {
Log.d("test","NOt OK RESULT");
}
}
But that is in the case when we know the TreeUri. Here in my case I know fle path and hence want to convert that into TreeUri.
To convert file path to uri use this:-
DocumentFile fileuri = DocumentFile.fromFile(new File(filepath));
Then you can perform delete,rename operations on this fileuri.
If you dont want to use ACTION_OPEN_DOCUMENT_TREE or ACTION_OPEN_DOCUMENT to get an Uri, you can convert FILE to Uri (SAF) using the following method valid from API19(Android4.4-Kitkat) to API28(Android8-Oreo). The returned Uri's are the same that return the dialog and its valid for API 28 security restrictions (SAF permissions), if you want to access external removable storage outside your application...
/**
* Ing.N.Nyerges 2019 V2.0
*
* Storage Access Framework(SAF) Uri's creator from File (java.IO),
* for removable external storages
*
* #param context Application Context
* #param file File path + file name
* #return Uri[]:
* uri[0] = SAF TREE Uri
* uri[1] = SAF DOCUMENT Uri
*/
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static Uri[] getSafUris (Context context, File file) {
Uri[] uri = new Uri[2];
String scheme = "content";
String authority = "com.android.externalstorage.documents";
// Separate each element of the File path
// File format: "/storage/XXXX-XXXX/sub-folder1/sub-folder2..../filename"
// (XXXX-XXXX is external removable number
String[] ele = file.getPath().split(File.separator);
// ele[0] = not used (empty)
// ele[1] = not used (storage name)
// ele[2] = storage number
// ele[3 to (n-1)] = folders
// ele[n] = file name
// Construct folders strings using SAF format
StringBuilder folders = new StringBuilder();
if (ele.length > 4) {
folders.append(ele[3]);
for (int i = 4; i < ele.length - 1; ++i) folders.append("%2F").append(ele[i]);
}
String common = ele[2] + "%3A" + folders.toString();
// Construct TREE Uri
Uri.Builder builder = new Uri.Builder();
builder.scheme(scheme);
builder.authority(authority);
builder.encodedPath("/tree/" + common);
uri[0] = builder.build();
// Construct DOCUMENT Uri
builder = new Uri.Builder();
builder.scheme(scheme);
builder.authority(authority);
if (ele.length > 4) common = common + "%2F";
builder.encodedPath("/document/" + common + file.getName());
uri[1] = builder.build();
return uri;
}
you have to set fullpath in renameTo method.
Use my example to work.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
Uri selectedImage = data.getData();
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(selectedImage,
filePathColumn, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String picturePath = cursor.getString(columnIndex);
cursor.close();
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setOnClickListener(this);
Bitmap bmp = null;
try {
bmp = getBitmapFromUri(selectedImage);
} catch (IOException e) {
e.printStackTrace();
}
imageView.setImageBitmap(bmp);
//get file
File photo = new File(picturePath);
//file name
String fileName = photo.getName();
//resave file with new name
File newFile = new File(photo.getParent() + "/fdtd." + fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()) );
photo.renameTo(newFile);
}
}
}
Remember that in my example i consider maintain the default extension of original file, because if you try to rename PNG to JPG you can have problem.
I have similar problems but I think I may have a solution. As per my experience using Windows MTP api. Which is very similar to Android SAF.
With SAF android doesnt want you to have direct access to files and instead gives you an ID to the files. If you check DocumentsContract.Document, you notice there is no column for the file path, only a display name.
However with recursion I think we can find the matching Uri. Sorry I cant give an example, but just simply walking the file tree using SAF api until you get all brances of your file path is the idea.
Related
I am using an intent ACTION_OPEN_DOCUMENT_TREE which in activity results gives a path to folder. But that path is not correct to search file from specified folder.
Context: I am building an app that allows user to select directory where program will search for the file type but the path received from data.Getdata() is not correct. So any idea how will it can be done?
My code:
#Override
protected void onCreate(Bundle savedInstanceState) {
//some lines of code
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, 0);}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data)
if (data != null) {
Uri uri_1= data.getData();
String path=uri_1.getPath();;
File custom_file=new File(path);
//searching for specific file type in given folder
ArrayList<File> my_files= fetch_files(custom_file);
//rest of code to what to do with files I received from fetch_files
}
I am using an intent ACTION_OPEN_DOCUMENT_TREE which in activity results gives a path to folder
No, it does not. It gives you a Uri to a document collection.
the path received from data.Getdata() is not correct
It is not a filesystem path. It is not supposed to be a filesystem path. For example, https://stackoverflow.com/questions/74062576/android-studio-how-to-get-storage-path-from-intent-action-open-document-tree is a Uri, and /questions/74062576/android-studio-how-to-get-storage-path-from-intent-action-open-document-tree not a filesystem path on your phone.
If you want to do things with that Uri, call DocumentFile.fromTreeUri(), passing in your Uri, to get a DocumentFile representing that document tree. DocumentFile gives you an API that resembles that of File, but works with documents and trees that are part of the Storage Access Framework.
It usually returns a path that starts with /tree/ so it needs to be converted into /storage/ path so that your files could be read. I found a code from a fellow member who did this (link:https://stackoverflow.com/a/65934369/20046611) but I modified it a little bit.
public String GetRealPath(Uri treeUri)
{
if (treeUri == null)
return "";
String path1=treeUri.getPath();
if (path1.startsWith("/tree/"))
{
String path2= path1.replace("/tree/","");
if (path2.startsWith("primary:"))
{
String primary = path2.replace("primary:","");
if (primary.contains(":"))
{
String storeName = "/storage/emulated/0/";
String[] last = path2.split(":");
String realPath = storeName + last[1];
return realPath;
}
else{
String storeName = "/storage/emulated/0/";
String[] last = path2.split(":");
String realPath = storeName + last[1];
return realPath;
}
}
else
{
if (path2.contains(":"))
{
String[] path3 = path2.split(":");
String storeName = path3[0];
String[] last = path2.split(":");
String realPath = "/" + storeName + "/" + last[1];
return realPath;
}
}
}
return path1;
}
all you need to do is enter String path=GetRealPath(uri_1); to get the path that will work.
TL:DR; I explained how to use create folders and subfolders using DocumentFile and how to delete file created using this class. Uri returned from onActvityResult() and documentFile.getUri.toString() are not same. My question is how to get a valid Uri to manipulate folders and files without using SAF UI, if possible not without using hack.
Let me share what i've learned so far and ask my questions.
If you want get Uri of folder and work on it, you should use Intent with ACTION_OPEN_DOCUMENT_TREE to get an Uri to access folders and set W/R permission for that uri.
Persistible permission granted onActivityResult with:
final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
If you select the main folder of device:
Uri treeUri = data.getData();
treeUri.toString()
Returns: content://com.android.externalstorage.documents/tree/primary:
File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), "");
Returns: storage/emulated/0
new File(treeUri.toString()).getAbsolutePath();
Returns: content:/com.android.externalstorage.documents/tree/primary:
If you use the DocumentFile class for getting path of the main folder you get
DocumentFile saveDir = null;
saveDir = DocumentFile.fromFile(Environment.getExternalStorageDirectory());
String uriString = saveDir.getUri().toString();
Returns: file:///storage/emulated/0
My first question is how can get the Uri with content using DocumentFile class.
I'm building a photography app and as default i'd like to set an initial folder for images using DocumentFile class.
#TargetApi(19)
protected DocumentFile getSaveDirMainMemory() {
DocumentFile saveDir = null;
saveDir = DocumentFile.fromFile(Environment.getExternalStorageDirectory());
// saveDir =
// DocumentFile.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
// saveDir =
// DocumentFile.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
DocumentFile newDir = null;
/*
* Check or create Main Folder
*/
// Check if main folder exist
newDir = saveDir.findFile(DIR_MAIN);
// Folder does not exist, create it
if (newDir == null || !newDir.exists()) {
newDir = saveDir.createDirectory(DIR_MAIN);
}
/*
* Check or create Sub-Folder
*/
DocumentFile newSubDir = null;
// Check if sub-folder exist
newSubDir = newDir.findFile(DIR_SUB);
// Folder does not exist, create it
if (newSubDir == null || !newSubDir.exists()) {
newSubDir = newDir.createDirectory(DIR_SUB);
}
if (newSubDir != null && newSubDir.exists()) {
return newSubDir;
} else if (newDir != null && newDir.exists()) {
return newDir;
} else {
return saveDir;
}
}
This method creates DIR_MAIN/DIR_SUB inside main memory of the device or PICTURES or DCIM folder depending on choice. Using this default folder i save images to this created sub folder.
I get newSubDir.getUri().toString(): file:///storage/emulated/0/MainFolder/SubFolder I named DIR_MAIN MainFolder, DIR_SUB: SubFolder to test.
To access or delete images i use this path and image name i created as
DocumentFile imageToDeletePath = DocumentFile.fromFile(new File(lastSavedImagePath));
DocumentFile imageToDelete = imageToDeletePath.findFile(lastSavedImageName);
imageDelete returns null because Uri is not in correct format.
If i open SAF ui and get UI onActivityResult and save it as string i use this method to get a directory and check Uri permissions
#TargetApi(19)
protected DocumentFile getSaveDirNew(String uriString) {
DocumentFile saveDir = null;
boolean canWrite = isUriWritePermission(uriString);
if (canWrite) {
try {
saveDir = DocumentFile.fromTreeUri(MainActivity.this, Uri.parse(uriString));
} catch (Exception e) {
saveDir = null;
}
}
return saveDir;
}
Check if Uri from string has write permission, it may not have if you don't take or remove persistable permissions.
private boolean isUriWritePermission(String uriString) {
boolean canWrite = false;
List<UriPermission> perms = getContentResolver().getPersistedUriPermissions();
for (UriPermission p : perms) {
if (p.getUri().toString().equals(uriString) && p.isWritePermission()) {
Toast.makeText(this, "canWrite() can write URI:: " + p.getUri().toString(), Toast.LENGTH_LONG).show();
canWrite = true;
break;
}
}
return canWrite;
}
After saving image with valid uri and using
DocumentFile imageToDeletePath = DocumentFile.fromTreeUri(this, Uri.parse(lastSavedImagePath));
DocumentFile imageToDelete = imageToDeletePath.findFile(lastSavedImageName);
Uri.fromFile() and DocumentFile.fromTreeUri() create uris from two different worlds.
While they currently look very similar, this is just coincidence and could change with any future Android release.
There is no "non-hacky" way to convert from one to the other. If you want a dirty solution, you can go for reflection (view the source code of DocumentFile.fromTreeUri and possibly use the Storage class on newer Android versions.
Also see:
Android - Storage Access Framework - Uri into local file
I need to get the name and extension of a file that I need to upload. I let the user select the file using an Intent call and get the URI as follows:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
/* stuff */
Uri uri = data.getData();
String filePath = data.getData().getPath();
Log.d("filePath ",filePath);
Log.d("URI ", uri.toString());
String fileName = (new File(filePath)).getName();
Log.d("fileName ",fileName);
but the results are as follows:
com.blah.blah D/URI: content://com.android.providers.downloads.documents/document/354
com.blah.blah D/filePath: /document/354
com.blah.blah D/fileName: 354
the name of the file isn't even 354! its a PDF file (say "Bus e-Ticket.pdf" or "Xender.apk")
String fileName = (new File(filePath)).getAbsolutePath();
also yields the same.
how can i get the file name/exstension on disk?
but the results are as follows
getPath() is pointless on a Uri with a content scheme. getPath() is pointless on a Uri with any scheme other than file.
the name of the file isn't even 354
There is no requirement that there be a file. https://stackoverflow.com/questions/46060367/android-filename-from-uri-is-not-the-same-as-actual-filename is a Uri, and I feel fairly confident that there is no file on a Stack Overflow server at the path /questions/46060367/android-filename-from-uri-is-not-the-same-as-actual-filename.
how can i get the file name/exstension on disk?
There is no requirement that the user choose something that is a file. You can use DocumentFile.fromSingleUri() and getName() to get a "display name" for the content identified by the Uri. That does not have to be a filename with an extension.
You can use the MediaStore class to obtain the filename seen by the user. In specific use the MediaColumns interface as shown below
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
ContentResolver cr = mctx.getContentResolver();
Cursor metaCursor = cr.query(uri[0], projection, null, null, null);
if (metaCursor != null) {
try {
if (metaCursor.moveToFirst()) {
realFileName = metaCursor.getString(0);
}
} finally {
metaCursor.close();
}
}
I have granted permission to write external and removable storages. But I still can't write everywhere on the sd card on Android 7.
Intent intent = storageVolume.createAccessIntent(null);
startActivityForResult(intent, 989);
;
RxPermissions.getInstance(getActivity())
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(granted -> {
if (!granted) return;
showAlternativeDirectoryChooser();//success
}, throwable -> {
});
Here is the code of folder creation:
DocumentFile documentFile = DocumentFile.fromFile(dir);
DocumentFile test = documentFile.createDirectory("test_folder");
test == null
You cannot use :
DocumentFile documentFile = DocumentFile.fromFile(dir);
Becouse its not a SAF valid Uri.
If you dont want to use ACTION_OPEN_DOCUMENT_TREE or ACTION_OPEN_DOCUMENT to get an Uri, you can convert FILE to Uri (SAF) using the following method valid from API19(Android4.4-Kitkat) to API28(Android8-Oreo). The returned Uri's are the same that return the dialog and its valid for API 28 security restrictions (SAF permissions), if you want to access external removable storage outside your application...
/**
* Ing.N.Nyerges 2019 V2.0
*
* Storage Access Framework(SAF) Uri's creator from File (java.IO),
* for removable external storages
*
* #param context Application Context
* #param file File path + file name
* #return Uri[]:
* uri[0] = SAF TREE Uri
* uri[1] = SAF DOCUMENT Uri
*/
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static Uri[] getSafUris (Context context, File file) {
Uri[] uri = new Uri[2];
String scheme = "content";
String authority = "com.android.externalstorage.documents";
// Separate each element of the File path
// File format: "/storage/XXXX-XXXX/sub-folder1/sub-folder2..../filename"
// (XXXX-XXXX is external removable number
String[] ele = file.getPath().split(File.separator);
// ele[0] = not used (empty)
// ele[1] = not used (storage name)
// ele[2] = storage number
// ele[3 to (n-1)] = folders
// ele[n] = file name
// Construct folders strings using SAF format
StringBuilder folders = new StringBuilder();
if (ele.length > 4) {
folders.append(ele[3]);
for (int i = 4; i < ele.length - 1; ++i) folders.append("%2F").append(ele[i]);
}
String common = ele[2] + "%3A" + folders.toString();
// Construct TREE Uri
Uri.Builder builder = new Uri.Builder();
builder.scheme(scheme);
builder.authority(authority);
builder.encodedPath("/tree/" + common);
uri[0] = builder.build();
// Construct DOCUMENT Uri
builder = new Uri.Builder();
builder.scheme(scheme);
builder.authority(authority);
if (ele.length > 4) common = common + "%2F";
builder.encodedPath("/document/" + common + file.getName());
uri[1] = builder.build();
return uri;
}
Then you can call the permissions using something like this:
context.grantUriPermission(context.getPackageName(), uri, Intent
.FLAG_GRANT_READ_URI_PERMISSION | Intent
.FLAG_GRANT_WRITE_URI_PERMISSION);
context.getContentResolver().takePersistableUriPermission(uri, Intent
.FLAG_GRANT_READ_URI_PERMISSION | Intent
.FLAG_GRANT_WRITE_URI_PERMISSION);
Android delivers the Uri you need to use in onActivityResult. That Uri is not necessarily related, at all, to the path you're trying to access. You cannot guess it. You have to actually use the Uri you're given.
If you call DocumentFile.fromFile or Uri.parse, then you are wrong.
I'm working on an application where the user is able to select files, either a new image from the camera, an image from the gallery, or a plain old file. It then shows an icon and the name for the selected item. I have this working with one exception. The gallery application integrates picasaweb pictures. If the user selects a picture from a picasa album, I'm not able to get a thumbnail for it.
I'm using the MediaStore.Images.Thumbnails.getThumbnail() method, and it works for other images in the gallery just fine, but for the picasaweb files, I get, regardless of what "kind" of thumbnail I attempt to get (although MICRO is what I'm after):
ERROR/MiniThumbFile(2051): Got exception when reading magic, id =
5634890756050069570, disk full or mount read-only? class
java.lang.IllegalArgumentException
I noticed the URI's given for the selected files are different. The local image files look like:
content://media/external/images/media/6912
and the picasaweb urls look like:
content://com.android.gallery3d.provider/picasa/item/5634890756050069570
I attempted to use a query to get at the raw THUMB_DATA, using Thumbnails.queryMiniThumbnails(), with Thumbnails.THUMB_DATA in the projection array, but I got a "no such column" error.
Is there another method for getting thumbnails that would work better? And will I have the same problem when I try and access the full image data?
What I have found is that on my Galaxy Nexus, the images for Picassa are stored in one of subdirectories under the /sdcard/Android/data/com.google.android.apps.plus/cache directory. When the content provider is com.google.android.gallery3d.provider then the number after "item" in the URL contains the name of the image (in your example above "5634890756050069570"). This data correspondes to a file in one of the subdirectories under /sdcard/Android/data/com.google.android.apps.plus/cache with the extension ".screen". If you were to copy this image from your phone (in your case 5634890756050069570.screen) using DDMS and rename it with the extension ".jpeg" you could open it and view it on your computer.
The following onActivityResult method will check for this content provider being returned, and then will recursively search for the file in the /sdcard/Android/data/com.google.android.apps.plus/cache directory. The private member variable fileSearchPathResults is filled in by the recursive search method walkDirectoryRecursivelySearchingForFile().
private String fileSearchPathResult = null;
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Uri selectedImage = data.getData();
String[] filePathColumn = { MediaStore.Images.Media.DATA };
String filePath = null;
// This code is required to get the image path on content providers
// on API > 10 where the image is from a picassa web album, since Google changed
// the content provider in versions with API > 10
if (selectedImage.toString().contains("com.google.android.gallery3d.provider")) {
StringBuilder contentProviderPath = new StringBuilder(selectedImage.toString());
int beginningIndex = contentProviderPath.lastIndexOf("/");
String fileNameWithoutExt = contentProviderPath.subSequence(beginningIndex + 1,
contentProviderPath.length()).toString();
Log.i(TAG, fileNameWithoutExt);
try {
File path = new File("/sdcard/Android/data/com.google.android.apps.plus/cache");
if (path.exists() && path.isDirectory()) {
fileSearchPathResult = null;
walkDirectoryRecursivelySearchingForFile(fileNameWithoutExt, path);
if (fileSearchPathResult != null) {
filePath = fileSearchPathResult;
}
}
} catch (Exception e) {
Log.i(TAG, "Picassa gallery content provider directory not found.");
}
}
}
public void walkDirectoryRecursivelySearchingForFile(String fileName, File dir) {
String pattern = fileName;
File listFile[] = dir.listFiles();
if (listFile != null) {
for (int i = 0; i < listFile.length; i++) {
if (listFile[i].isDirectory()) {
walkDirectoryRecursivelySearchingForFile(fileName, listFile[i]);
} else {
if (listFile[i].getName().contains(pattern)) {
fileSearchPathResult = listFile[i].getPath();
}
}
}
}
}
With the filePath, you can create a Bitmap of the image with the following code:
Bitmap sourceImageBitmap = BitmapFactory.decodeFile(filePath);
ACTIVITYRESULT_CHOOSEPICTURE is the int you use when calling startActivity(intent, requestCode);
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == ACTIVITYRESULT_CHOOSEPICTURE) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
final InputStream is = context.getContentResolver().openInputStream(intent.getData());
final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
is.close();
}
}
That code will load the whole image. You can adjust the sample size to something reasonable to get a thumbnail sized image.