Android 11 Action_Open_Document_Tree: Set Initial Uri to the Documents Folder

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



Leave a reply



Submit