Android 11 has no Root access to the data directory, Android 11 accesses the data directory, Android 11 releases the restrictions on the data directory, and Android 11 solves the problem of data blank

Posted by NCC1701 on Tue, 08 Mar 2022 18:33:13 +0100

Turn:

Android 11 has no Root access to the data directory, Android 11 accesses the data directory, Android 11 releases the restrictions on the data directory, and Android 11 solves the problem of data blank

Implementation of Android 11 without Root accessing data directory

  • Start of text
    • About Android 11 permission changes
    • How can an ordinary Android user easily and quickly access the Android/data directory
    • How can developers access the Data directory without ROOT
    • Officially start solving Android/data problems
    • Get permission for a file directory
    • Callback and permanently save the permission of a directory
    • Access directories through DocumentFile Api
    • Implement traversal or management of Android/data file directory
    • Important pit: why not use Path directly to browse files?
    • Solution
    • Disadvantages of SAF scheme
    • Enlarge the trick, and directly unlock the ROOT permission and then access the Data directory with the right
    • epilogue
    • Encapsulated tool class

Start of text

About Android 11 permission changes

Google has adopted the file sandbox storage mode in Android 11 and above systems, which makes third-party applications unable to access the Android/data directory as before, which is a good thing. But what I can't understand is why the APP that has obtained the permission of "all file management" is still limited. Isn't it completely left to cleaning and file management software? Really shouldn't!

How can an ordinary Android user easily and quickly access the Android/data directory

As we all know, it is very inconvenient not to access the Android/data directory, such as managing QQ, files received by wechat and data downloaded by other apps (such as Xunlei, etc.).

Now the application I developed has realized no Root access to Android/data directory (including file browser function), and can be managed conveniently.

https://www.coolapk.com/apk/com.magicalstory.cleaner

Software download

Welcome Android mobile phone users to download and use and Android developers to download the preview function.

App interface Preview

How can developers access the Data directory without ROOT

1. First, you can obtain all file management permissions as needed:
Declare in the list:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission
        android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

2. Dynamic access to read and write permissions. Needless to say, if you feel troublesome, you can use Guo Lin's permissionX library
Github

About manage all files permission
This permission allows your App to access all files (except Android/data directory) through the File API as before Android 11

If necessary, declare in the manifest that sandbox storage is not enabled

        android:preserveLegacyExternalStorage="true"
        android:requestLegacyExternalStorage="true"

Relevant judgment

   //Determine whether all file permissions are required
            if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager())) {
     
     
            //Indicates that you already have this permission
            }

Get permission

  Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
            startActivity(intent);

Officially start solving Android/data problems

First, the SAF framework (Android Storage Access Framework) is used
This framework is in Android 4 4 is introduced. If you haven't learned about it, you can baidu.

Get permission for a file directory

The method is very simple, using Android Intent. action. OPEN_ DOCUMENT_ The Intent of tree (call the file selector of SAF framework to select a folder) can be authorized
The tool class will be released later. Now let's take a look at the following example:

//Gets access to the specified directory
 public static void startFor(String path, Activity context, int REQUEST_CODE_FOR_DIR) {
     
     
        statusHolder.path = path;//Here is mainly a state saving class of mine, which indicates that the path to obtain permission is him, so you don't have to worry about it.
        String uri = changeToUri(path);//Call the method to convert the path into parseable uri text. This method will be published below
        Uri parse = Uri.parse(uri);
        Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT_TREE");
        intent.addFlags(
                Intent.FLAG_GRANT_READ_URI_PERMISSION
                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                        | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                        | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
     
     
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, parse);
        }
        context.startActivityForResult(intent, REQUEST_CODE_FOR_DIR);//Start authorization
    }

Schematic diagram after calling:

Callback and permanently save the permission of a directory

    //Return authorization status
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
     
     
        super.onActivityResult(requestCode, resultCode, data);
        Uri uri;

        if (data == null) {
     
     
            return;
        }

        if (requestCode == REQUEST_CODE_FOR_DIR && (uri = data.getData()) != null) {
     
     
            getContentResolver().takePersistableUriPermission(uri, data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));//The key here is to save the access permission of this directory
            PreferencesUtil.saveString(MainActivity.this, statusHolder.path + "to grant authorization", "true");//I handle the logic by myself. Don't worry about it

        }

    }

Permission authorization and permanent saving succeeded

Access directories through DocumentFile Api

It's very simple to use
Let's see how to generate DocumentFile objects first

DocumentFile documentFile = DocumentFile.fromTreeUri(context, Uri.parse(fileUriUtils.changeToUri3(path)));
//changeToUri3 method is my encapsulated method, which will be used later. This is a method to generate a specified resolvable URI through path

Just call DocumentFile Just use the fromtreeuri () method, which means to generate a DocumentFile object from a folder URI (treeUri is the folder URI)

Of course, there are other ways:
DocumentFile.fromSingleUri();
DocumentFile.fromFile();
DocumentFile.isDocumentUri();

Just look at the name, but what we have is a folder uri. Of course, this method is used to generate DocumentFile objects. DocumentFile objects generated by different methods have different effects. If you use fromTreeUri to generate folder objects by default, there is a ListFiles() method
DocumentFile.ListFiles() lists all the sub files in the folder, similar to file Listfiles() method

Then in this way, when you get the DocumentFile object, you can perform operations, such as listing sub files, deleting files, moving, deleting and so on. Yes, that's how the Android/data directory is operated and accessed!

Implement traversal or management of Android/data file directory

More basic, I will not say more, simply talk about the implementation scheme and the pits stepped on.

1. Traversal is no different from ordinary full traversal, but it cannot be traversed by directly passing in Path

    //Traverse the example without additional logical processing
    void getFiles(DocumentFile documentFile) {
     
     
        Log.d("file:", documentFile.getName());
        if (documentFile.isDirectory()) {
     
     
            for (DocumentFile file : documentFile.listFiles()) {
     
     
                Log.d("Sub file", file.getName());
                if (file.isDirectory()) {
     
     
                    getFiles(file);//Recursive call
                }
            }
            
        }
    }

2. Implement the file manager scheme (this is the scheme for managing Android/data directory)
Only the methods are described below

 class file{
     
     
        String title;
        DocumentFile documentFile;

        public String getTitle() {
     
     
            return title;
        }

        public void setTitle(String title) {
     
     
            this.title = title;
        }

        public DocumentFile getDocumentFile() {
     
     
            return documentFile;
        }

        public void setDocumentFile(DocumentFile documentFile) {
     
     
            this.documentFile = documentFile;
        }
    }

    MainActivity{
     
     
        //Load data
        void getFiles(DocumentFile documentFile) {
     
     
            ArrayList<file> arrayList = new ArrayList<>();
            if (documentFile.isDirectory()) {
     
     
                for (DocumentFile documentFile_inner : documentFile.listFiles()) {
     
     
                    file file = new file();
                    file.setTitle(documentFile_inner.getName());
                    file.setDocumentFile(documentFile_inner);
                }
            }
        }
        }
    }

When the list is clicked, the processing scheme:

  public void onclick(int postion){
     
     
       file file = arrayList.get(postion);
       getFiles(file.getDocumentFile());//Get the document object of the folder, and then traverse the folder
       //Then show it again and it's done
   }

The above is to simulate the implementation of file manager - > file browsing function. You should be clear at a glance and only introduce the scheme.

File management I implemented (no root management of data directory directly on Android 11)

Important pit: why not use Path directly to browse files?

Yes, obviously, isn't it more convenient to use the traditional file path to realize file management?
I feel the same way. When I was adapting Android 11, in order to make small changes, I definitely wanted to use this method to adapt, but it didn't work!

Didn't we get the permission of Android/data directory? It is clearly stated that after obtaining the permission of the directory, you have the read-write permission of the folder and all sub files!
Why can't I directly convert the path into uri by calling changToUri, and then generate the DocumentFile object?
Isn't that more convenient? Moreover, the File efficiency of SAF is much lower than that of File.
But after trying several times, I'm sure it won't work!

Even if you generate the correct URI of the sub file of the Android/data directory and then generate the DocumentFile object, you still can't, because the DocumentFile object you generate always points to Android/data (that is, the directory you authorized). There is no solution!

At first, I thought the URI I generated was incorrect, but when I tried to authorize the subdirectory path I wanted to obtain again, the DocumentFile object generated with the same URI can point to the correct directory.

See here, you should understand that Google restricts the unauthorized subfolder directory, which prevents you from directly generating the correct Docment object through TreeUri, at least in the Android/data directory.

Now do you think Google's official explanation: after obtaining the permission of the directory, you have the read-write permission of the folder and all sub files!
Is it farting? Indeed!

Solution

Since we can't directly generate the DocumentFile object of the subdirectory of the authorized directory, can I try to generate the DocumentFile object (non treeUri) directly corresponding to the sub path? Let's try the fromSingleUri() method:

    //Obtain the document file according to the path
    public static DocumentFile getDoucmentFile(Context context, String path) {
     
     
        if (path.endsWith("/")) {
     
     
            path = path.substring(0, path.length() - 1);
        }
        String path2 = path.replace("/storage/emulated/0/", "").replace("/", "%2F");
        return DocumentFile.fromSingleUri(context, Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata/document/primary%3A" + path2));
    }

Obviously, it's OK! The correct DocumentFile object can be generated, and we can use it to do some fun things, such as directly generating DocumentFile object through path, obtaining the size of a file, judging the existence state, and so on.
After Android/data on Android 11 is limited, I think this is a good solution. After all, it can realize Root free access and management.

Disadvantages of SAF scheme

Obviously, the speed and efficiency of accessing files through SAF file storage framework is much lower than that of File API, because SAF is not used to access Android11/data directory files.

But for some apps involving file management, this is the most complete or better solution at present.

Enlarge the trick, and directly unlock the ROOT permission and then access the Data directory with the right

Execute with ROOT permission
"chmod -R 777 /storage/emulated/0/Android/data"
Command can unlock the Android/data directory. Note: irreversible.

As for how to access the directory through ROOT permission, you need to refer to MT file manager or Zhang haidashen's open source file manager

Github
Github:https://github.com/zhanghai/MaterialFiles

epilogue

The above is my solution. It has completely solved the problem of Android 11 system accessing Android/data. If there is a problem, you can leave a message. I will reply. If you have a better solution, please leave a message in the comment area and I will update it in time.

Of course, this scheme will be a little unsatisfactory, but it is the best way in the scheme. After all, Google restricts you from accessing the data directory, and some of our applications involving file management do need to be accessed. The scheme is available for personal testing. I have adapted Android 11 in my app according to the above scheme, which is not satisfactory.

My App:
Software download
https://www.coolapk.com/apk/com.magicalstory.cleaner
Welcome to download the experience.

Encapsulated tool class

Because the personal project is still in operation, it is not convenient to open source all the code to GitHub, so let's release the tool class for everyone to use.
It's really super simple. You can start it by looking at it carefully. It's all daily operations. For all the big guys, it's just a hand.

public class fileUriUtils {
     
     
    public static String root = Environment.getExternalStorageDirectory().getPath() + "/";

    public static String treeToPath(String path) {
     
     
        String path2;
        if (path.contains("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata/document/primary")) {
     
     
            path2 = path.replace("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata/document/primary%3A", root);
            path2 = path2.replace("%2F", "/");
        } else {
     
     
            path2 = root + textUtils.getSubString(path + "test", "document/primary%3A", "test").replace("%2F", "/");

        }
        return path2;
    }


    //Judge whether the Data permission has been obtained, and change the logic to judge other directories. You can understand it
    public static boolean isGrant(Context context) {
     
     
        for (UriPermission persistedUriPermission : context.getContentResolver().getPersistedUriPermissions()) {
     
     
            if (persistedUriPermission.isReadPermission() && persistedUriPermission.getUri().toString().equals("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata")) {
     
     
                return true;
            }
        }
        return false;
    }

    //Directly return to DocumentFile
    public static DocumentFile getDocumentFilePath(Context context, String path, String sdCardUri) {
     
     
        DocumentFile document = DocumentFile.fromTreeUri(context, Uri.parse(sdCardUri));
        String[] parts = path.split("/");
        for (int i = 3; i < parts.length; i++) {
     
     
            document = document.findFile(parts[i]);
        }
        return document;
    }

    //Path converted to uriTree
    public static String changeToUri(String path) {
     
     
        if (path.endsWith("/")) {
     
     
            path = path.substring(0, path.length() - 1);
        }
        String path2 = path.replace("/storage/emulated/0/", "").replace("/", "%2F");
        return "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata/document/primary%3A" + path2;
    }

    //Path converted to uriTree
    public static DocumentFile getDoucmentFile(Context context, String path) {
     
     
        if (path.endsWith("/")) {
     
     
            path = path.substring(0, path.length() - 1);
        }
        String path2 = path.replace("/storage/emulated/0/", "").replace("/", "%2F");
        return DocumentFile.fromSingleUri(context, Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata/document/primary%3A" + path2));
    }


    //Path converted to uriTree
    public static String changeToUri2(String path) {
     
     
        String[] paths = path.replaceAll("/storage/emulated/0/Android/data", "").split("/");
        StringBuilder stringBuilder = new StringBuilder("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata/document/primary%3AAndroid%2Fdata");
        for (String p : paths) {
     
     
            if (p.length() == 0) continue;
            stringBuilder.append("%2F").append(p);
        }
        return stringBuilder.toString();

    }


    //Path converted to uriTree
    public static String changeToUri3(String path) {
     
     
        path = path.replace("/storage/emulated/0/", "").replace("/", "%2F");
        return ("content://com.android.externalstorage.documents/tree/primary%3A" + path);

    }

//Get permissions for the specified directory
    public static void startFor(String path, Activity context, int REQUEST_CODE_FOR_DIR) {
     
     
        statusHolder.path = path;
        String uri = changeToUri(path);
        Uri parse = Uri.parse(uri);
        Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT_TREE");
        intent.addFlags(
                Intent.FLAG_GRANT_READ_URI_PERMISSION
                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                        | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                        | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
     
     
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, parse);
        }
        context.startActivityForResult(intent, REQUEST_CODE_FOR_DIR);

    }

//Obtain data permission directly. This scheme is recommended
    public static void startForRoot(Activity context, int REQUEST_CODE_FOR_DIR) {
     
     
        Uri uri1 = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata");
//        DocumentFile documentFile = DocumentFile.fromTreeUri(context, uri1);
        String uri = changeToUri(Environment.getExternalStorageDirectory().getPath());
        uri = uri + "/document/primary%3A" + Environment.getExternalStorageDirectory().getPath().replace("/storage/emulated/0/", "").replace("/", "%2F");
        Uri parse = Uri.parse(uri);
        DocumentFile documentFile = DocumentFile.fromTreeUri(context, uri1);
        Intent intent1 = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        intent1.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
        intent1.putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentFile.getUri());
        context.startActivityForResult(intent1, REQUEST_CODE_FOR_DIR);

    }

}

Turn:

Android 11 has no Root access to the data directory, Android 11 accesses the data directory, Android 11 releases the restrictions on the data directory, and Android 11 solves the problem of data blank


--Posted from Rpc