Android 11 ACTION_OPEN_DOCUMENT_TREE: set initial URI to the Documents folder
We will manupilate INITIAL_URI
obtained from StorageManager..getPrimaryStorageVolume().createOpenDocumentTreeIntent()
.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Intent intent = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
//String startDir = "Android";
//String startDir = "Download"; // Not choosable on an Android 11 device
//String startDir = "DCIM";
//String startDir = "DCIM/Camera"; // replace "/", "%2F"
//String startDir = "DCIM%2FCamera";
String startDir = "Documents";
Uri uri = intent.getParcelableExtra("android.provider.extra.INITIAL_URI");
String scheme = uri.toString();
Log.d(TAG, "INITIAL_URI scheme: " + scheme);
scheme = scheme.replace("/root/", "/document/");
scheme += "%3A" + startDir;
uri = Uri.parse(scheme);
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
Log.d(TAG, "uri: " + uri.toString());
((Activity) context).startActivityForResult(intent, REQUEST_ACTION_OPEN_DOCUMENT_TREE);
return;
}
How can I set the ACTION_OPEN_DOCUMENT_TREE start path the first time a user uses my app?
We manupulate INITIAL_URI of .createOpenDocumentTreeIntent().
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Intent intent = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
//String startDir = "Android";
//String startDir = "Download"; // Not choosable on an Android 11 device
//String startDir = "DCIM";
//String startDir = "DCIM/Camera"; // replace "/", "%2F"
String startDir = "DCIM%2FCamera";
Uri uri = intent.getParcelableExtra("android.provider.extra.INITIAL_URI");
String scheme = uri.toString();
Log.d(TAG, "INITIAL_URI scheme: " + scheme);
scheme = scheme.replace("/root/", "/document/");
scheme += "%3A" + startDir;
uri = Uri.parse(scheme);
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
Log.d(TAG, "uri: " + uri.toString());
((Activity) context).startActivityForResult(intent, REQUEST_ACTION_OPEN_DOCUMENT_TREE);
return;
}
The logs will print something like:
INITIAL_URI scheme: content://com.android.externalstorage.documents/root/primary
uri: content://com.android.externalstorage.documents/document/primary%3ADownload
SAF with initial path - prevent changing initial path in manager
How can I block it to one specific path?
That is not possible, sorry. It is the user's device, and the user's content. The user gets to decide what tree to share with your app (if any).
How to open a Folder/directory from Notifications in Android
After a thorough searching, I found an answer that works for me..
// my notification function
fun showNotification(){
// an intent to be initialized when:
lateinit var intent: Intent
// A) Android version is 10 or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val sm = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
intent = sm.primaryStorageVolume.createOpenDocumentTreeIntent()
var startDir = "Android%2Fmedia" // here is the directory you wanted to open. feel free to test based on your needs.
var uri: Uri = intent.getParcelableExtra("android.provider.extra.INITIAL_URI")!!
var scheme = uri.toString()
scheme = scheme.replace("/root/", "/document/");
scheme += "%3A$startDir";
uri = Uri.parse(scheme);
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
// B) Android Versions 9 and below
} else {
val path = Environment.getExternalStorageDirectory().toString() + "/Android/media/yourpackagename" // path of the directory you wanted to open
val uri = Uri.parse(path)
intent = Intent(Intent.ACTION_PICK)
intent.putExtra("android.provider.extra.INITIAL_URI", uri)
intent.setDataAndType(uri, "*/*")
}
val activityPendingIntent = PendingIntent.getActivity(
context,
1,
intent,
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
)
// shows notification
val notification = NotificationCompat.Builder(context, COUNTER_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_download_done)
.setContentTitle("Download Finished")
.setContentText("Locate at Files:Android/media/net.pregi.netmesh2/NetMesh")
.setContentIntent(activityPendingIntent)
.build()
notificationManager.notify(1, notification)
}
Note that for android 9 and below, I only managed to open the folder, but couldn't manage to open the file inside it. Still looking for an answer to this. For reference, here are the links that I've found related to this matter. Still open for suggestions.
Android 11 ACTION_OPEN_DOCUMENT_TREE
How to open specific folder in the storage using intent in android
Android open DocumentPicker directly in Downloads directory
EXTRA_INITIAL_URI works for ACTION_OPEN_DOCUMENT but not for ACTION_OPEN_DOCUMENT_TREE.
You can put the uries of documents the user previous choosed.
For ACTION_OPEN_DOCUMENT_TREE you can let the picker open on primary partion or de secondary (removable sd card) without using that extra. Only on Android Q+ i think.
There is nothing to let the latter open in the directories you want as far as i know.
How to save Tempfile to External storage using ACTION_OPEN_DOCUMENT_TREE?
So Asking for a file instead of directory to be nicer to the user and lessen overwriting issues.
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Create a file with the requested MIME type.
intent.setType(mimeType);
// Suggest a filename
intent.putExtra(Intent.EXTRA_TITLE, "abc.zip");
// You can suggest a starting directory here see note about this later
//intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
// OK, this is deprecated and user probably has set their own request code
startActivityForResult(intent, 601);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == 601 && resultCode == RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.
// Pull that URI using resultData.getData().
Uri uri;
if (resultData != null) {
uri = resultData.getData();
new BgExecuter().execute(new Runnable() {
@Override
public void run() {
// Now that the Uri returned is for a file not a directory, your exist "saveFile" method should work
copyFileOut(PDFTools.this, selectedFile, Uri);
//selectedFile is that file that is saved by default in the Temp directory
}
});
}
}
}
private Boolean copyFileOut(Context context,File copyFile, Uri uri){
BufferedInputStream bis;
BufferedOutputStream bos = null;
// Now read the file
try{
InputStream input = new FileInputStream(copyFile);
int originalSize = input.available();
bis = new BufferedInputStream(input);
bos = new BufferedOutputStream(context.getContentResolver().openOutputStream(uri));
byte[] buf = new byte[originalSize];
//noinspection ResultOfMethodCallIgnored
bis.read(buf);
do {
bos.write(buf);
} while (bis.read(buf) != -1);
bis.close();
} catch (Exception e) {
if (BuildConfig.LOG) {
Log.e(Constants.TAG, "copyFileOut:" + e);
}
// Notify User of fail
return false;
} finally {
try {
if (bos != null) {
bos.flush();
bos.close();
}
} catch (Exception ignored) {
}
}
return true;
}
Note for initial start directory see How I set the ACTION_OPEN_DOCUMENT_TREE start path the first time a user uses my app?
Also note that in your "saveFile" method
InputStream inputStream = context.getContentResolver().openInputStream(Uri.fromFile(selectedFile));
could be replaced with
InputStream inputStream = new FileInputStream(selectedFile);
As you say that this file is in your Apps private Cache directory, so won't have any permissions problems.
Related Topics
How to Get File Path from Sd Card in Android
Android Overlay to Grab All Touch, and Pass Them On
How to Send Notification to Specific Users with Fcm
How to Programmatically Launch a Specific Application
Display Data After Every 10 Seconds in Android
Get the Distance Between Two Locations in Android
Setting System Time of Rooted Phone
How to Remove a Contact Programmatically in Android
Equivalent of Clean & Build in Android Studio
What Is the Alternative to Android Orientation Sensor
Android Set Button Background Programmatically
Android Make Animated Video from List of Images
Generating Google Map Release API Key
Android Replace the Current Fragment with Another Fragment
Modifying the Android Seekbar Widget to Operate Vertically
How to Record Video from Background of Application:Android
How to Modify Ripple Color When Using Attr/Selectableitembackground as Background