Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 94c9181f authored by android-build-team Robot's avatar android-build-team Robot
Browse files

Snap for 4574286 from 5729be86 to pi-release

Change-Id: Ie39ab3be004a8facddb2a73a54c23eb6417536fb
parents 99759af7 5729be86
Loading
Loading
Loading
Loading
+42 −164
Original line number Original line Diff line number Diff line
@@ -32,6 +32,7 @@ import static com.android.documentsui.ScopedAccessMetrics.logInvalidScopedAccess
import static com.android.documentsui.ScopedAccessMetrics.logValidScopedAccessRequest;
import static com.android.documentsui.ScopedAccessMetrics.logValidScopedAccessRequest;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
import static com.android.documentsui.base.SharedMinimal.DIRECTORY_ROOT;
import static com.android.documentsui.base.SharedMinimal.DIRECTORY_ROOT;
import static com.android.documentsui.base.SharedMinimal.getUriPermission;
import static com.android.documentsui.base.SharedMinimal.getInternalDirectoryName;
import static com.android.documentsui.base.SharedMinimal.getInternalDirectoryName;
import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK_AGAIN;
import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK_AGAIN;
import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_NEVER_ASK;
import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_NEVER_ASK;
@@ -154,96 +155,20 @@ public class ScopedAccessActivity extends Activity {
     */
     */
    private static boolean showFragment(ScopedAccessActivity activity, int userId,
    private static boolean showFragment(ScopedAccessActivity activity, int userId,
            StorageVolume storageVolume, String directoryName) {
            StorageVolume storageVolume, String directoryName) {
        if (DEBUG)
        return getUriPermission(activity,
            Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
                activity.getExternalStorageClient(), storageVolume, directoryName, userId, true,
                    + directoryName + ", and user " + userId);
                (file, volumeLabel, isRoot, isPrimary, grantedUri, rootUri) -> {
        final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
        final boolean isPrimary = storageVolume.isPrimary();

        if (isRoot && isPrimary) {
            if (DEBUG) Log.d(TAG, "root access requested on primary volume");
            return false;
        }

        final File volumeRoot = storageVolume.getPathFile();
        File file;
        try {
            file = isRoot ? volumeRoot : new File(volumeRoot, directoryName).getCanonicalFile();
        } catch (IOException e) {
            Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
                    + " and directory " + directoryName);
            logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
            return false;
        }
        final StorageManager sm =
                (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE);

        final String root, directory;
        if (isRoot) {
            root = volumeRoot.getAbsolutePath();
            directory = ".";
        } else {
            root = file.getParent();
            directory = file.getName();
            // Verify directory is valid.
            if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
                if (DEBUG)
                    Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
                            + file.getAbsolutePath() + "')");
                logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY);
                return false;
            }
        }

        // Gets volume label and converted path.
        String volumeLabel = null;
        String volumeUuid = null;
        final List<VolumeInfo> volumes = sm.getVolumes();
        if (DEBUG) Log.d(TAG, "Number of volumes: " + volumes.size());
        File internalRoot = null;
        boolean found = true;
        for (VolumeInfo volume : volumes) {
            if (isRightVolume(volume, root, userId)) {
                found = true;
                internalRoot = volume.getInternalPathForUser(userId);
                // Must convert path before calling getDocIdForFileCreateNewDir()
                if (DEBUG) Log.d(TAG, "Converting " + root + " to " + internalRoot);
                file = isRoot ? internalRoot : new File(internalRoot, directory);
                volumeUuid = storageVolume.getUuid();
                volumeLabel = sm.getBestVolumeDescription(volume);
                if (TextUtils.isEmpty(volumeLabel)) {
                    volumeLabel = storageVolume.getDescription(activity);
                }
                if (TextUtils.isEmpty(volumeLabel)) {
                    volumeLabel = activity.getString(android.R.string.unknownName);
                    Log.w(TAG, "No volume description  for " + volume + "; using " + volumeLabel);
                }
                break;
            }
        }
        if (internalRoot == null) {
            // Should not happen on normal circumstances, unless app crafted an invalid volume
            // using reflection or the list of mounted volumes changed.
            Log.e(TAG, "Didn't find right volume for '" + storageVolume.dump() + "' on " + volumes);
            return false;
        }

                    // Checks if the user has granted the permission already.
                    // Checks if the user has granted the permission already.
        final Intent intent = getIntentForExistingPermission(activity, internalRoot, file);
                    final Intent intent = getIntentForExistingPermission(activity,
                            activity.getCallingPackage(), grantedUri, rootUri);
                    if (intent != null) {
                    if (intent != null) {
            logValidScopedAccessRequest(activity, directory,
                        logValidScopedAccessRequest(activity, isRoot ? "." : directoryName,
                                SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED);
                                SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED);
                        activity.setResult(RESULT_OK, intent);
                        activity.setResult(RESULT_OK, intent);
                        activity.finish();
                        activity.finish();
                        return true;
                        return true;
                    }
                    }


        if (!found) {
            Log.e(TAG, "Could not get volume for " + file);
            logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
            return false;
        }

                    // Gets the package label.
                    // Gets the package label.
                    final String appLabel = getAppLabel(activity);
                    final String appLabel = getAppLabel(activity);
                    if (appLabel == null) {
                    if (appLabel == null) {
@@ -255,7 +180,7 @@ public class ScopedAccessActivity extends Activity {
                    final Bundle args = new Bundle();
                    final Bundle args = new Bundle();
                    args.putString(EXTRA_FILE, file.getAbsolutePath());
                    args.putString(EXTRA_FILE, file.getAbsolutePath());
                    args.putString(EXTRA_VOLUME_LABEL, volumeLabel);
                    args.putString(EXTRA_VOLUME_LABEL, volumeLabel);
        args.putString(EXTRA_VOLUME_UUID, volumeUuid);
                    args.putString(EXTRA_VOLUME_UUID, storageVolume.getUuid());
                    args.putString(EXTRA_APP_LABEL, appLabel);
                    args.putString(EXTRA_APP_LABEL, appLabel);
                    args.putBoolean(EXTRA_IS_ROOT, isRoot);
                    args.putBoolean(EXTRA_IS_ROOT, isRoot);
                    args.putBoolean(EXTRA_IS_PRIMARY, isPrimary);
                    args.putBoolean(EXTRA_IS_PRIMARY, isPrimary);
@@ -268,6 +193,8 @@ public class ScopedAccessActivity extends Activity {
                    ft.commitAllowingStateLoss();
                    ft.commitAllowingStateLoss();


                    return true;
                    return true;

                });
    }
    }


    private static String getAppLabel(Activity activity) {
    private static String getAppLabel(Activity activity) {
@@ -282,52 +209,9 @@ public class ScopedAccessActivity extends Activity {
        }
        }
    }
    }


    private static boolean isRightVolume(VolumeInfo volume, String root, int userId) {
        final File userPath = volume.getPathForUser(userId);
        final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
        final boolean isMounted = volume.isMountedReadable();
        if (DEBUG)
            Log.d(TAG, "Volume: " + volume
                    + "\n\tuserId: " + userId
                    + "\n\tuserPath: " + userPath
                    + "\n\troot: " + root
                    + "\n\tpath: " + path
                    + "\n\tisMounted: " + isMounted);

        return isMounted && root.equals(path);
    }

    private static Uri getGrantedUriPermission(Context context, ContentProviderClient provider,
            File file) {
        // Calls ExternalStorageProvider to get the doc id for the file
        final Bundle bundle;
        try {
            bundle = provider.call("getDocIdForFileCreateNewDir", file.getPath(), null);
        } catch (RemoteException e) {
            Log.e(TAG, "Did not get doc id from External Storage provider for " + file, e);
            logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
            return null;
        }
        final String docId = bundle == null ? null : bundle.getString("DOC_ID");
        if (docId == null) {
            Log.e(TAG, "Did not get doc id from External Storage provider for " + file);
            logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
            return null;
        }
        if (DEBUG) Log.d(TAG, "doc id for " + file + ": " + docId);

        final Uri uri = DocumentsContract.buildTreeDocumentUri(Providers.AUTHORITY_STORAGE, docId);
        if (uri == null) {
            Log.e(TAG, "Could not get URI for doc id " + docId);
            return null;
        }
        if (DEBUG) Log.d(TAG, "URI for " + file + ": " + uri);
        return uri;
    }

    private static Intent createGrantedUriPermissionsIntent(Context context,
    private static Intent createGrantedUriPermissionsIntent(Context context,
            ContentProviderClient provider, File file) {
            ContentProviderClient provider, File file) {
        final Uri uri = getGrantedUriPermission(context, provider, file);
        final Uri uri = getUriPermission(context, provider, file);
        return createGrantedUriPermissionsIntent(uri);
        return createGrantedUriPermissionsIntent(uri);
    }
    }


@@ -341,19 +225,13 @@ public class ScopedAccessActivity extends Activity {
        return intent;
        return intent;
    }
    }


    private static Intent getIntentForExistingPermission(ScopedAccessActivity activity, File root,
    private static Intent getIntentForExistingPermission(Context context, String packageName,
            File file) {
            Uri grantedUri, Uri rootUri) {
        final String packageName = activity.getCallingPackage();
        if (DEBUG) {
        final ContentProviderClient storageClient = activity.getExternalStorageClient();
        final Uri grantedUri = getGrantedUriPermission(activity, storageClient, file);
        final Uri rootUri = root.equals(file) ? grantedUri
                : getGrantedUriPermission(activity, storageClient, root);

        if (DEBUG)
            Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri
            Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri
                    + " or its root (" + rootUri + ")");
                    + " or its root (" + rootUri + ")");
        final ActivityManager am =
        }
                (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
        final ActivityManager am = context.getSystemService(ActivityManager.class);
        for (GrantedUriPermission uriPermission : am.getGrantedUriPermissions(packageName)
        for (GrantedUriPermission uriPermission : am.getGrantedUriPermissions(packageName)
                .getList()) {
                .getList()) {
            final Uri uri = uriPermission.uri;
            final Uri uri = uriPermission.uri;
+188 −1
Original line number Original line Diff line number Diff line
@@ -16,10 +16,30 @@


package com.android.documentsui.base;
package com.android.documentsui.base;


import static android.os.Environment.isStandardDirectory;

import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_ERROR;
import static com.android.documentsui.ScopedAccessMetrics.SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY;
import static com.android.documentsui.ScopedAccessMetrics.logInvalidScopedAccessRequest;

import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.ContentProviderClient;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;


import java.io.File;
import java.io.IOException;
import java.util.List;

/**
/**
 * Contains the minimum number of utilities (contants, helpers, etc...) that can be used by both the
 * Contains the minimum number of utilities (contants, helpers, etc...) that can be used by both the
 * main package and the minimal APK that's used by Android TV (and other devices).
 * main package and the minimal APK that's used by Android TV (and other devices).
@@ -34,9 +54,32 @@ public final class SharedMinimal {
    public static final boolean DEBUG = Build.IS_DEBUGGABLE;
    public static final boolean DEBUG = Build.IS_DEBUGGABLE;
    public static final boolean VERBOSE = DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
    public static final boolean VERBOSE = DEBUG && Log.isLoggable(TAG, Log.VERBOSE);


    // Special directory name representing the full volume of a scoped directory request.
    /**
     * Special directory name representing the full volume of a scoped directory request.
     */
    public static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";
    public static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";


    /**
     * Callback for {@link SharedMinimal#getUriPermission(Context, ContentProviderClient,
     * StorageVolume, String, int, boolean, GetUriPermissionCallback)}.
     */
    public static interface GetUriPermissionCallback {

        /**
         * Evaluates the result of the request.
         *
         * @param file the path of the requested URI.
         * @param volumeLabel user-friendly label of the volume.
         * @param isRoot whether the requested directory is the root directory.
         * @param isPrimary whether the requested volume is the primary storage volume.
         * @param requestedUri the requested URI.
         * @param rootUri the URI for the volume's root directory.
         * @return whethe the result was sucessfully.
         */
        boolean onResult(File file, String volumeLabel, boolean isRoot, boolean isPrimary,
                Uri requestedUri, Uri rootUri);
    }

    /**
    /**
     * Gets the name of a directory name in the format that's used internally by the app
     * Gets the name of a directory name in the format that's used internally by the app
     * (i.e., mapping {@code null} to {@link #DIRECTORY_ROOT});
     * (i.e., mapping {@code null} to {@link #DIRECTORY_ROOT});
@@ -55,6 +98,150 @@ public final class SharedMinimal {
        return name.equals(DIRECTORY_ROOT) ? null : name;
        return name.equals(DIRECTORY_ROOT) ? null : name;
    }
    }


    /**
     * Gets the URI permission for the given volume and directory.
     *
     * @param context caller's context.
     * @param storageClient storage provider client.
     * @param storageVolume volume.
     * @param directoryName directory name, or {@link #DIRECTORY_ROOT} for full volume.
     * @param userId caller's user handle.
     * @param logMetrics whether intermediate errors should be logged.
     * @param callback callback that receives the results.
     *
     * @return whether the call was succesfull or not.
     */
    public static boolean getUriPermission(Context context,
            ContentProviderClient storageClient, StorageVolume storageVolume,
            String directoryName, int userId, boolean logMetrics,
            GetUriPermissionCallback callback) {
        if (DEBUG) {
            Log.d(TAG, "getUriPermission() for volume " + storageVolume.dump() + ", directory "
                    + directoryName + ", and user " + userId);
        }
        final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
        final boolean isPrimary = storageVolume.isPrimary();

        if (isRoot && isPrimary) {
            if (DEBUG) Log.d(TAG, "root access requested on primary volume");
            return false;
        }

        final File volumeRoot = storageVolume.getPathFile();
        File file;
        try {
            file = isRoot ? volumeRoot : new File(volumeRoot, directoryName).getCanonicalFile();
        } catch (IOException e) {
            Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
                    + " and directory " + directoryName);
            if (logMetrics) logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
            return false;
        }
        final StorageManager sm = context.getSystemService(StorageManager.class);

        final String root, directory;
        if (isRoot) {
            root = volumeRoot.getAbsolutePath();
            directory = ".";
        } else {
            root = file.getParent();
            directory = file.getName();
            // Verify directory is valid.
            if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
                if (DEBUG) {
                    Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
                            + file.getAbsolutePath() + "')");
                }
                if (logMetrics) {
                    logInvalidScopedAccessRequest(context,
                            SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY);
                }
                return false;
            }
        }

        // Gets volume label and converted path.
        String volumeLabel = null;
        final List<VolumeInfo> volumes = sm.getVolumes();
        if (DEBUG) Log.d(TAG, "Number of volumes: " + volumes.size());
        File internalRoot = null;
        for (VolumeInfo volume : volumes) {
            if (isRightVolume(volume, root, userId)) {
                internalRoot = volume.getInternalPathForUser(userId);
                // Must convert path before calling getDocIdForFileCreateNewDir()
                if (DEBUG) Log.d(TAG, "Converting " + root + " to " + internalRoot);
                file = isRoot ? internalRoot : new File(internalRoot, directory);
                volumeLabel = sm.getBestVolumeDescription(volume);
                if (TextUtils.isEmpty(volumeLabel)) {
                    volumeLabel = storageVolume.getDescription(context);
                }
                if (TextUtils.isEmpty(volumeLabel)) {
                    volumeLabel = context.getString(android.R.string.unknownName);
                    Log.w(TAG, "No volume description  for " + volume + "; using " + volumeLabel);
                }
                break;
            }
        }
        if (internalRoot == null) {
            // Should not happen on normal circumstances, unless app crafted an invalid volume
            // using reflection or the list of mounted volumes changed.
            Log.e(TAG, "Didn't find right volume for '" + storageVolume.dump() + "' on " + volumes);
            return false;
        }

        final Uri requestedUri = getUriPermission(context, storageClient, file);
        final Uri rootUri = internalRoot.equals(file) ? requestedUri
                : getUriPermission(context, storageClient, internalRoot);

        return callback.onResult(file, volumeLabel, isRoot, isPrimary, requestedUri, rootUri);
    }

    /**
     * Creates an URI permission for the given file.
     */
    public static Uri getUriPermission(Context context, ContentProviderClient storageProvider,
            File file) {
        // Calls ExternalStorageProvider to get the doc id for the file
        final Bundle bundle;
        try {
            bundle = storageProvider.call("getDocIdForFileCreateNewDir", file.getPath(), null);
        } catch (RemoteException e) {
            Log.e(TAG, "Did not get doc id from External Storage provider for " + file, e);
            logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
            return null;
        }
        final String docId = bundle == null ? null : bundle.getString("DOC_ID");
        if (docId == null) {
            Log.e(TAG, "Did not get doc id from External Storage provider for " + file);
            logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
            return null;
        }
        if (DEBUG) Log.d(TAG, "doc id for " + file + ": " + docId);

        final Uri uri = DocumentsContract.buildTreeDocumentUri(Providers.AUTHORITY_STORAGE, docId);
        if (uri == null) {
            Log.e(TAG, "Could not get URI for doc id " + docId);
            return null;
        }
        if (DEBUG) Log.d(TAG, "URI for " + file + ": " + uri);
        return uri;
    }

    private static boolean isRightVolume(VolumeInfo volume, String root, int userId) {
        final File userPath = volume.getPathForUser(userId);
        final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
        final boolean isMounted = volume.isMountedReadable();
        if (DEBUG)
            Log.d(TAG, "Volume: " + volume
                    + "\n\tuserId: " + userId
                    + "\n\tuserPath: " + userPath
                    + "\n\troot: " + root
                    + "\n\tpath: " + path
                    + "\n\tisMounted: " + isMounted);

        return isMounted && root.equals(path);
    }

    private SharedMinimal() {
    private SharedMinimal() {
        throw new UnsupportedOperationException("provides static fields only");
        throw new UnsupportedOperationException("provides static fields only");
    }
    }