alexrintt / shared-storage

Flutter plugin to work with Android external storage.
http://alexrintt.io/shared-storage/
MIT License
53 stars 24 forks source link

Do I need to configure anything in AndroidManifest.xml? #117

Closed shinexoh closed 1 year ago

shinexoh commented 1 year ago

I want to use the openDocumentTree method to authorize the Android/data/somedirectory/ Directory Then use the getDocumentContentmethod to read or write sub files in the directory, so I don't need to authorize the files I want to read and write again through the openDocumentmethod.

Because my current situation is that if I edit the file using another app (an app of the file management editing type), the sub file permissions that I authorize using the openDocumentmethod within my application will become invalid, and then I need to call the openDocumentmethod again to let the user select the file to regain read and write permissions.

So I want to make sure that my application won't invalidate my file permissions when other applications edit the file. Do I need to configure something in AndroidManifest.xml? How can I achieve this

alexrintt commented 1 year ago

So I want to make sure that my application won't invalidate my file permissions when other applications edit the file.

This is the default behavior, once the user grant permission over a Uri, you have permission over all subfiles (recursively), so even if other app edit a file that is in under your granted tree, you still have access to it, also applies for created files, what you want to achieve is what is supposed to happen.

I need some code and details to know what is exactly happening in your case, can you show how you are using and what is your target Android version?


A side-note: Android/data folder is not permitted to be selected anymore (even if the user wants!) on higher Android versions (12+), so be aware of this limitation (same applies for MANAGE_EXTERNAL_STORAGE permission) see https://www.reddit.com/r/Android/comments/wru35i/clearing_up_confusion_about_how_to_access/.

shinexoh commented 1 year ago

Android does not allow you to select Android/data/, but you can select a subdirectory within it. Just set the following Uri for the initialUri, and I can successfully select android/data/mark.via/ content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fmark.via

This is the variable I declare for the directory Uri

final dUri = Uri.parse(
      'content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fmark.via');

This is to obtain the Uri variable of the "app. ini" file in the directory

final fUri = Uri.parse(
      'content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fmark.via%2Ffiles%2Flogs%2Fapp.ini');

This is the method to execute the authorization directory:

  void _get() async {
    final Uri? grantedUri = await openDocumentTree(
      initialUri: dUri,
    );
  }

This is the string execution method for viewing files:

  void _read() async {
    try {
      final readFile = await getDocumentContent(fUri);
      print(String.fromCharCodes(readFile!));
    } catch (e) {
      print(e);
    }
  }

This is my method for authorizing sub files (click to manually select the file)

  void _getFile() async {
    final file = await openDocument(
      initialUri: dUri,
    );
  }

After executing the _get method to authorize the directory, I successfully obtained permissions for the directory However, when I execute _readto attempt to view content, I am still not allowed to view it (directly exiting the application crashes), so I must execute the _getFilemethod to manually select files to obtain authorization. only in this way can I successfully execute_ read

Other applications edit sub files in the authorized directory, and my permissions will disappear (I mean _getFileauthorization sub file permissions). The persistedUriPermissionsmethod will reduce the length once from the original length, as well as in the phone settings.

My device is Android 13 targetSdkVersion 29

alexrintt commented 1 year ago

TL;DR: Try declaring the tree owner of your document at fUri.

final fUri = Uri.parse(
- 'content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata%2Fmark.via%2Ffiles%2Flogs%2Fapp.ini'
+ 'content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fmark.via/document/primary%3AAndroid%2Fdata%2Fmark.via%2Ffiles%2Flogs%2Fapp.ini'
);

What I could understand from your functions is that:

So here is an example that creates your dUri and fUri correctly and can possible fix your issue:

void main() {
  // Granted tree uri:
  final dUri = Uri(
    scheme: 'content',
    host: 'com.android.externalstorage.documents',
    path: '/tree/primary%3AAndroid%2Fdata%2Fmark.via',
  );

  // content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fmark.via
  print(dUri);

  // Sub-file uri:
  final fUri = dUri.replace(
    path: dUri.path +
        '/document/primary%3AAndroid%2Fdata%2Fmark.via%2Ffiles%2Flogs%2Fapp.ini',
  );

  // content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fmark.via/document/primary%3AAndroid%2Fdata%2Fmark.via%2Ffiles%2Flogs%2Fapp.ini
  print(fUri);
}
shinexoh commented 1 year ago

Thank you. Now that the problem has been resolved, I have been using the Uri returned by the openDocumentselection sub file as a parameter until the problem has been resolved. Now I wonder how you got this new Uri? Because in the future, I will have to select other sub files. How do I generate this Uri?

alexrintt commented 1 year ago

TD;DR: Follow these steps:

  1. Use the following template:
    content://com.android.externalstorage.documents/tree/{encodedTreeUri}/document/{encodedSubfileUri}
  2. Replace encodedTreeUri with the URL encoded version of primary:{path-to-your-desired-storage-tree}, in your case: primary:Android/data/mark.via (The path must not start with a /).
  3. Replace encodedSubfileUri with the URL encoded version of primary:{path-to-your-desired-storage-file}, in your case: primary:Android/data/mark.via/files/logs/app.ini (The path must not start with a /).
  4. The final result is the subfile (aka child file) URI.

Dart implementation:

final Uri dUri = createTreeUri('Android/data/mark.via');
final Uri fUri =
    createDocumentUri(dUri, 'Android/data/mark.via/files/logs/app.ini');

Uri createTreeUri(
  String treePath, {
  String host = 'com.android.externalstorage.documents',
  String scheme = 'content',
  String storage = 'primary',
}) {
  assert(
    !treePath.startsWith('/'),
    'Please, remove `/` from the start of your [treePath].',
  );

  final Uri treeUri = Uri(
    host: host,
    scheme: scheme,
    path: '/tree/${Uri.encodeComponent('$storage:$treePath')}',
  );

  return treeUri;
}

Uri createDocumentUri(
  Uri treeUri,
  String documentPath, {
  String storage = 'primary',
}) {
  assert(
    !documentPath.startsWith('/'),
    'Please, remove `/` from the start of your [documentPath].',
  );

  final Uri documentUri = treeUri.replace(
    path:
        '${treeUri.path}/document/${Uri.encodeComponent('$storage:$documentPath')}',
  );

  return documentUri;
}

I must warn that this is specific for your use-case: generating a pre-defined Uri from a pre-defined path that will be in the primary storage of the device using the default android storage provider. This will not work for all use-cases, like generating a URI for a cloud storage provider or even a custom storage provider. This also may not work for some devices.

shinexoh commented 1 year ago

thanks

shinexoh commented 1 year ago

Bro, I have encountered a problem again and I want to seek your help. I successfully granted the directory using OpenDocumentTree, and I also used the sub file Uri you generated for me before. I can use the Uri of the sub file to operate on it successfully, but it only works for a short period of time (it may be because I restarted my phone because I am using the AS virtual machine). Afterwards, whenever I call the method to operate on the sub file, I will be prompted:

W/DocumentFile(13899): Failed query: java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fmark.via%2Fdemo/document/primary%3AAndroid%2Fdata%2Fmark.via%2Fdemo/children from pid=13899, uid=10166 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
8
W/DocumentFile(13899): Failed query: java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2Fmark.via%2Fdemo/document/primary%3AAndroid%2Fdata%2Fmark.via%2Fdemo%2FUserCustom.ini%20(4) from pid=13899, uid=10166 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

I have already set up OpenDocumentTree

grantWritePermission = true
persistablePermission = true

And the data returned by my call to persistedUriPermissionsalso includes the directory Uri I authorized, I don't understand why this is happening. My current solution is to grant directory permissions again whenever there is an error, but this does not solve the root cause of the problem

alexrintt commented 1 year ago

Do not re-use closed issues, open a new one.