summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/storage/StorageVolume.java11
-rw-r--r--packages/DocumentsUI/res/values/strings.xml3
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java3
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/Metrics.java12
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java91
-rw-r--r--proto/src/metrics_constants.proto5
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';