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

Commit b4fb50f8 authored by Felipe Leme's avatar Felipe Leme Committed by android-build-merger
Browse files

Merge "Allow Scoped Directory Access on whole volume." into nyc-dev

am: 9eb5555a

* commit '9eb5555a':
  Allow Scoped Directory Access on whole volume.
parents d707fa3d 9eb5555a
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -306,8 +306,8 @@ public final class StorageVolume implements Parcelable {
    }

    /**
     * Builds an intent to give access to a standard storage directory after obtaining the user's
     * approval.
     * Builds an intent to give access to a standard storage directory or entire volume after
     * obtaining the user's approval.
     * <p>
     * When invoked, the system will ask the user to grant access to the requested directory (and
     * its descendants). The result of the request will be returned to the activity through the
@@ -322,12 +322,17 @@ public final class StorageVolume implements Parcelable {
     * {@link Context#getExternalCacheDirs()}, or
     * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
     *
     * <strong>NOTE: </strong>requesting access to the entire volume is not recommended and it will
     * result in a stronger message displayed to the user, which may cause the user to reject
     * the request.
     *
     * @param directoryName must be one of
     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}, or
     * {code null} to request access to the entire volume.
     *
     * @see DocumentsContract
     */
+3 −0
Original line number Diff line number Diff line
@@ -204,6 +204,9 @@
    <string name="open_external_dialog_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
        access to <xliff:g id="directory" example="Pictures"><i>^2</i></xliff:g> directory on
        <xliff:g id="storage" example="SD Card"><i>^3</i></xliff:g>?</string>
    <!-- Text in an alert dialog asking user to grant app access to all data in an external storage volume -->
    <string name="open_external_dialog_root_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
        access to your data, including photos and videos, on <xliff:g id="storage" example="SD Card"><i>^2</i></xliff:g>?</string>
    <!-- Checkbox that allows user to not be questioned about the directory access request again -->
    <string name="never_ask_again">Don\'t ask again</string>
    <!-- Text in the button asking user to allow access to a given directory. -->
+0 −3
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.documentsui;

import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.Shared.TAG;
import static com.android.documentsui.State.MODE_UNKNOWN;

import java.lang.annotation.Retention;
@@ -29,7 +27,6 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.util.Log;

import com.android.documentsui.State.ViewMode;
import com.android.documentsui.model.RootInfo;
+8 −4
Original line number Diff line number Diff line
@@ -427,12 +427,16 @@ public final class Metrics {
    public static void logValidScopedAccessRequest(Activity activity, String directory,
            @ScopedAccessGrant int type) {
        int index = -1;
        if (OpenExternalDirectoryActivity.DIRECTORY_ROOT.equals(directory)) {
            index = -2;
        } else {
            for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) {
                if (STANDARD_DIRECTORIES[i].equals(directory)) {
                    index = i;
                    break;
                }
            }
        }
        final String packageName = activity.getCallingPackage();
        switch (type) {
            case SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED:
+57 −34
Original line number Diff line number Diff line
@@ -85,6 +85,9 @@ public class OpenExternalDirectoryActivity extends Activity {
    private static final String EXTRA_APP_LABEL = "com.android.documentsui.APP_LABEL";
    private static final String EXTRA_VOLUME_LABEL = "com.android.documentsui.VOLUME_LABEL";
    private static final String EXTRA_VOLUME_UUID = "com.android.documentsui.VOLUME_UUID";
    private static final String EXTRA_IS_ROOT = "com.android.documentsui.IS_ROOT";
    // Special directory name representing the full volume
    static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";

    private ContentProviderClient mExternalStorageClient;

@@ -114,13 +117,9 @@ public class OpenExternalDirectoryActivity extends Activity {
            finish();
            return;
        }
        final String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME);
        String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME );
        if (directoryName == null) {
            logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
            if (DEBUG) Log.d(TAG, "missing extra " + EXTRA_DIRECTORY_NAME + " on " + intent);
            setResult(RESULT_CANCELED);
            finish();
            return;
            directoryName = DIRECTORY_ROOT;
        }
        final StorageVolume volume = (StorageVolume) storageVolume;
        if (getScopedAccessPermissionStatus(getApplicationContext(), getCallingPackage(),
@@ -157,9 +156,11 @@ public class OpenExternalDirectoryActivity extends Activity {
        if (DEBUG)
            Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
                    + directoryName + ", and user " + userId);
        final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
        final File volumeRoot = storageVolume.getPathFile();
        File file;
        try {
            file = new File(storageVolume.getPathFile(), directoryName).getCanonicalFile();
            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);
@@ -169,9 +170,13 @@ public class OpenExternalDirectoryActivity extends Activity {
        final StorageManager sm =
                (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE);

        final String root = file.getParent();
        final String directory = file.getName();

        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)
@@ -180,18 +185,20 @@ public class OpenExternalDirectoryActivity extends Activity {
                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;
        for (VolumeInfo volume : volumes) {
            if (isRightVolume(volume, root, userId)) {
                final File internalRoot = volume.getInternalPathForUser(userId);
                internalRoot = volume.getInternalPathForUser(userId);
                // Must convert path before calling getDocIdForFileCreateNewDir()
                if (DEBUG) Log.d(TAG, "Converting " + root + " to " + internalRoot);
                file = new File(internalRoot, directory);
                file = isRoot ? internalRoot : new File(internalRoot, directory);
                volumeLabel = sm.getBestVolumeDescription(volume);
                volumeUuid = volume.getFsUuid();
                break;
@@ -199,7 +206,7 @@ public class OpenExternalDirectoryActivity extends Activity {
        }

        // Checks if the user has granted the permission already.
        final Intent intent = getIntentForExistingPermission(activity, file);
        final Intent intent = getIntentForExistingPermission(activity, isRoot, internalRoot, file);
        if (intent != null) {
            logValidScopedAccessRequest(activity, directory,
                    SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED);
@@ -227,6 +234,7 @@ public class OpenExternalDirectoryActivity extends Activity {
        args.putString(EXTRA_VOLUME_LABEL, volumeLabel);
        args.putString(EXTRA_VOLUME_UUID, volumeUuid);
        args.putString(EXTRA_APP_LABEL, appLabel);
        args.putBoolean(EXTRA_IS_ROOT, isRoot);

        final FragmentManager fm = activity.getFragmentManager();
        final FragmentTransaction ft = fm.beginTransaction();
@@ -310,19 +318,27 @@ public class OpenExternalDirectoryActivity extends Activity {
    }

    private static Intent getIntentForExistingPermission(OpenExternalDirectoryActivity activity,
            File file) {
            boolean isRoot, File root, File file) {
        final String packageName = activity.getCallingPackage();
        final Uri grantedUri =
                getGrantedUriPermission(activity, activity.getExternalStorageClient(), file);
        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 + ")");
        final ActivityManager am =
                (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
        for (UriPermission uriPermission : am.getGrantedUriPermissions(packageName).getList()) {
            final Uri uri = uriPermission.getUri();
            if (uri.equals(grantedUri)) {
            if (uri == null) {
                Log.w(TAG, "null URI for " + uriPermission);
                continue;
            }
            if (uri.equals(grantedUri) || uri.equals(rootUri)) {
                if (DEBUG) Log.d(TAG, packageName + " already has permission: " + uriPermission);
                return createGrantedUriPermissionsIntent(uri);
                return createGrantedUriPermissionsIntent(grantedUri);
            }
        }
        if (DEBUG) Log.d(TAG, packageName + " does not have permission for " + grantedUri);
@@ -335,6 +351,7 @@ public class OpenExternalDirectoryActivity extends Activity {
        private String mVolumeUuid;
        private String mVolumeLabel;
        private String mAppLabel;
        private boolean mIsRoot;
        private CheckBox mDontAskAgain;
        private OpenExternalDirectoryActivity mActivity;
        private AlertDialog mDialog;
@@ -349,6 +366,7 @@ public class OpenExternalDirectoryActivity extends Activity {
                mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
                mVolumeLabel = args.getString(EXTRA_VOLUME_LABEL);
                mAppLabel = args.getString(EXTRA_APP_LABEL);
                mIsRoot = args.getBoolean(EXTRA_IS_ROOT);
            }
            mActivity = (OpenExternalDirectoryActivity) getActivity();
        }
@@ -375,6 +393,7 @@ public class OpenExternalDirectoryActivity extends Activity {
                mActivity = (OpenExternalDirectoryActivity) getActivity();
            }
            final String directory = mFile.getName();
            final String directoryName = mIsRoot ? DIRECTORY_ROOT : directory;
            final Context context = mActivity.getApplicationContext();
            final OnClickListener listener = new OnClickListener() {

@@ -386,17 +405,17 @@ public class OpenExternalDirectoryActivity extends Activity {
                                mActivity.getExternalStorageClient(), mFile);
                    }
                    if (which == DialogInterface.BUTTON_NEGATIVE || intent == null) {
                        logValidScopedAccessRequest(mActivity, directory,
                        logValidScopedAccessRequest(mActivity, directoryName,
                                SCOPED_DIRECTORY_ACCESS_DENIED);
                        final boolean checked = mDontAskAgain.isChecked();
                        if (checked) {
                            logValidScopedAccessRequest(mActivity, directory,
                                    SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST);
                            setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
                                    mVolumeUuid, directory, PERMISSION_NEVER_ASK);
                                    mVolumeUuid, directoryName, PERMISSION_NEVER_ASK);
                        } else {
                            setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
                                    mVolumeUuid, directory, PERMISSION_ASK_AGAIN);
                                    mVolumeUuid, directoryName, PERMISSION_ASK_AGAIN);
                        }
                        mActivity.setResult(RESULT_CANCELED);
                    } else {
@@ -408,13 +427,17 @@ public class OpenExternalDirectoryActivity extends Activity {
                }
            };

            final CharSequence message = TextUtils
                    .expandTemplate(
                            getText(R.string.open_external_dialog_request), mAppLabel, directory,
                            mVolumeLabel);
            @SuppressLint("InflateParams")
            // It's ok pass null ViewRoot on AlertDialogs.
            final View view = View.inflate(mActivity, R.layout.dialog_open_scoped_directory, null);
            final CharSequence message;
            if (mIsRoot) {
                message = TextUtils.expandTemplate(getText(
                        R.string.open_external_dialog_root_request), mAppLabel, mVolumeLabel);
            } else {
                message = TextUtils.expandTemplate(getText(R.string.open_external_dialog_request),
                        mAppLabel, directory, mVolumeLabel);
            }
            final TextView messageField = (TextView) view.findViewById(R.id.message);
            messageField.setText(message);
            mDialog = new AlertDialog.Builder(mActivity, R.style.Theme_AppCompat_Light_Dialog_Alert)
@@ -425,7 +448,7 @@ public class OpenExternalDirectoryActivity extends Activity {

            mDontAskAgain = (CheckBox) view.findViewById(R.id.do_not_ask_checkbox);
            if (getScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
                    mVolumeUuid, directory) == PERMISSION_ASK_AGAIN) {
                    mVolumeUuid, directoryName) == PERMISSION_ASK_AGAIN) {
                mDontAskAgain.setVisibility(View.VISIBLE);
                mDontAskAgain.setOnCheckedChangeListener(new OnCheckedChangeListener() {

Loading