diff options
6 files changed, 81 insertions, 44 deletions
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 54d20d361a26..17176ecf1c7c 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -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 */ diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 0d098e69b73b..b26ee9778d33 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -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. --> diff --git a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java index 8c4859f52a02..2315664f0d8a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java +++ b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java @@ -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; diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java index deef1c278e63..929d1e0d0ac6 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java @@ -427,10 +427,14 @@ public final class Metrics { public static void logValidScopedAccessRequest(Activity activity, String directory, @ScopedAccessGrant int type) { int index = -1; - for (int i = 0; i < STANDARD_DIRECTORIES.length; i++) { - if (STANDARD_DIRECTORIES[i].equals(directory)) { - index = i; - break; + 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(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java index 2b6f3968e203..dd7eaf99ad84 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java @@ -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,16 +170,21 @@ public class OpenExternalDirectoryActivity extends Activity { final StorageManager sm = (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE); - final String root = file.getParent(); - final String 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; + 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. @@ -186,12 +192,13 @@ public class OpenExternalDirectoryActivity extends Activity { 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() { diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index d929519bddff..d6f1499ef684 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -1922,10 +1922,12 @@ message MetricsEvent { // User granted access to the request folder; action takes an integer // representing the folder's index on Environment.STANDARD_DIRECTORIES + // (or -2 for root access, or -1 or unknown directory). ACTION_SCOPED_DIRECTORY_ACCESS_GRANTED_BY_FOLDER = 326; // User denied access to the request folder; action takes an integer // representing the folder's index on Environment.STANDARD_DIRECTORIES + // (or -2 for root access, or -1 or unknown directory). ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_BY_FOLDER = 327; // User granted access to the request folder; action pass package name @@ -1939,6 +1941,7 @@ message MetricsEvent { // App requested access to a directory it has already been granted // access before; action takes an integer representing the folder's // index on Environment.STANDARD_DIRECTORIES + // (or -2 for root access, or -1 or unknown directory). ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED_BY_FOLDER = 330; // App requested access to a directory it has already been granted @@ -1998,6 +2001,7 @@ message MetricsEvent { // User already denied access to the request folder; action takes an integer // representing the folder's index on Environment.STANDARD_DIRECTORIES + // (or -2 for root access, or -1 or unknown directory). ACTION_SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED_BY_FOLDER = 353; // User already denied access to the request folder; action pass package name @@ -2006,6 +2010,7 @@ message MetricsEvent { // User denied access to the request folder and checked 'Do not ask again'; // action takes an integer representing the folder's index on Environment.STANDARD_DIRECTORIES + // (or -2 for root access, or -1 or unknown directory). ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_FOLDER = 355; // User denied access to the request folder and checked 'Do not ask again'; |