Loading src/com/android/documentsui/ScopedAccessActivity.java +4 −2 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.GrantedUriPermission; import android.content.ContentProviderClient; import android.content.Context; import android.content.DialogInterface; Loading Loading @@ -353,8 +354,9 @@ public class ScopedAccessActivity extends Activity { + " 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(); for (GrantedUriPermission uriPermission : am.getGrantedUriPermissions(packageName) .getList()) { final Uri uri = uriPermission.uri; if (uri == null) { Log.w(TAG, "null URI for " + uriPermission); continue; Loading src/com/android/documentsui/ScopedAccessProvider.java +224 −45 Original line number Diff line number Diff line Loading @@ -27,26 +27,33 @@ import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABL import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_VOLUME_UUID; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.getInternalDirectoryName; import static com.android.documentsui.base.SharedMinimal.getExternalDirectoryName; import static com.android.documentsui.base.SharedMinimal.getInternalDirectoryName; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK_AGAIN; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_GRANTED; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_NEVER_ASK; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.getAllPackages; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.getAllPermissions; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.setScopedAccessPermissionStatus; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.statusAsString; import static com.android.internal.util.Preconditions.checkArgument; import android.app.ActivityManager; import android.app.GrantedUriPermission; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Environment; import android.provider.DocumentsContract; import android.util.ArraySet; import android.util.Log; import com.android.documentsui.base.Providers; import com.android.documentsui.prefs.ScopedAccessLocalPreferences.Permission; import com.android.internal.util.ArrayUtils; Loading @@ -54,9 +61,11 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; //TODO(b/72055774): update javadoc once implementation is finished /** Loading Loading @@ -120,55 +129,122 @@ public class ScopedAccessProvider extends ContentProvider { case URI_PACKAGES: return getPackagesCursor(); case URI_PERMISSIONS: return getPermissionsCursor(selectionArgs); if (ArrayUtils.isEmpty(selectionArgs)) { throw new UnsupportedOperationException("selections cannot be empty"); } // For simplicity, we only support one package (which is what Settings is passing). if (selectionArgs.length > 1) { Log.w(TAG, "Using just first entry of " + Arrays.toString(selectionArgs)); } return getPermissionsCursor(selectionArgs[0]); default: throw new UnsupportedOperationException("Unsupported Uri " + uri); } } private Cursor getPackagesCursor() { // First get the packages that were denied final Set<String> pkgs = getAllPackages(getContext()); final Context context = getContext(); // First, get the packages that were denied final Set<String> pkgs = getAllPackages(context); // Second, query AM to get all packages that have a permission. final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); final List<GrantedUriPermission> amPkgs = am.getGrantedUriPermissions(null).getList(); if (!amPkgs.isEmpty()) { amPkgs.forEach((perm) -> pkgs.add(perm.packageName)); } if (ArrayUtils.isEmpty(pkgs)) { if (DEBUG) Log.v(TAG, "getPackagesCursor(): ignoring " + pkgs); if (DEBUG) Log.v(TAG, "getPackagesCursor(): nothing to do" ); return null; } // TODO(b/63720392): also need to query AM for granted permissions if (DEBUG) { Log.v(TAG, "getPackagesCursor(): denied=" + pkgs + ", granted=" + amPkgs); } // Then create the cursor // Finally, create the cursor final MatrixCursor cursor = new MatrixCursor(TABLE_PACKAGES_COLUMNS, pkgs.size()); pkgs.forEach((pkg) -> cursor.addRow( new Object[] { pkg })); return cursor; } private Cursor getPermissionsCursor(String[] packageNames) { // First get the packages that were denied final List<Permission> rawPermissions = getAllPermissions(getContext()); // TODO(b/63720392): need to unit tests to handle scenarios where the root permission of // a secondary volume mismatches a child permission (for example, child is allowed by root // is denied). private Cursor getPermissionsCursor(String packageName) { final Context context = getContext(); if (ArrayUtils.isEmpty(rawPermissions)) { if (DEBUG) Log.v(TAG, "getPermissionsCursor(): ignoring " + rawPermissions); return null; // List of volumes that were granted by AM at the root level - in that case, // we can ignored individual grants from AM or denials from our preferences final Set<String> grantedVolumes = new ArraySet<>(); // List of directories (mapped by volume uuid) that were granted by AM so they can be // ignored if also found on our preferences final Map<String, Set<String>> grantedDirsByUuid = new HashMap<>(); // Cursor rows final List<Object[]> permissions = new ArrayList<>(); // First, query AM to get all packages that have a permission. final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); final List<GrantedUriPermission> uriPermissions = am.getGrantedUriPermissions(packageName).getList(); if (DEBUG) { Log.v(TAG, "am returned =" + uriPermissions); } setGrantedPermissions(packageName, uriPermissions, permissions, grantedVolumes, grantedDirsByUuid); // TODO(b/72055774): unit tests for filters (permissions and/or package name); final List<Object[]> permissions = rawPermissions.stream() .filter(permission -> ArrayUtils.contains(packageNames, permission.pkg) && permission.status == PERMISSION_NEVER_ASK) .map(permission -> new Object[] { permission.pkg, permission.uuid, getExternalDirectoryName(permission.directory), Integer.valueOf(0) }) .collect(Collectors.toList()); // Now gets the packages that were denied final List<Permission> rawPermissions = getAllPermissions(context); // TODO(b/63720392): need to add logic to handle scenarios where the root permission of // a secondary volume mismatches a child permission (for example, child is allowed by root // is denied). if (DEBUG) { Log.v(TAG, "rawPermissions: " + rawPermissions); } // Merge the permissions granted by AM with the denied permissions saved on our preferences. for (Permission rawPermission : rawPermissions) { if (!packageName.equals(rawPermission.pkg)) { if (DEBUG) { Log.v(TAG, "ignoring " + rawPermission + " because package is not " + packageName); } continue; } if (rawPermission.status != PERMISSION_NEVER_ASK && rawPermission.status != PERMISSION_ASK_AGAIN) { // We only care for status where the user denied a request. if (DEBUG) { Log.v(TAG, "ignoring " + rawPermission + " because of its status"); } continue; } if (grantedVolumes.contains(rawPermission.uuid)) { if (DEBUG) { Log.v(TAG, "ignoring " + rawPermission + " because whole volume is granted"); } continue; } final Set<String> grantedDirs = grantedDirsByUuid.get(rawPermission.uuid); if (grantedDirs != null && grantedDirs.contains(rawPermission.directory)) { Log.w(TAG, "ignoring " + rawPermission + " because it was granted already"); continue; } permissions.add(new Object[] { packageName, rawPermission.uuid, getExternalDirectoryName(rawPermission.directory), 0 }); } // TODO(b/63720392): also need to query AM for granted permissions if (DEBUG) { Log.v(TAG, "total permissions: " + permissions.size()); } // Then create the cursor final MatrixCursor cursor = new MatrixCursor(TABLE_PERMISSIONS_COLUMNS, permissions.size()); Loading @@ -176,6 +252,108 @@ public class ScopedAccessProvider extends ContentProvider { return cursor; } /** * Converts the permissions returned by AM and add it to 3 buckets ({@code permissions}, * {@code grantedVolumes}, and {@code grantedDirsByUuid}). * * @param packageName name of package that the permissions were granted to. * @param uriPermissions permissions returend by AM * @param permissions list of permissions that can be converted to a {@link #TABLE_PERMISSIONS} * row. * @param grantedVolumes volume uuids that were granted full access. * @param grantedDirsByUuid directories that were granted individual acces (key is volume uuid, * value is list of directories). */ private void setGrantedPermissions(String packageName, List<GrantedUriPermission> uriPermissions, List<Object[]> permissions, Set<String> grantedVolumes, Map<String, Set<String>> grantedDirsByUuid) { final List<Permission> grantedPermissions = parseGrantedPermissions(uriPermissions); for (Permission p : grantedPermissions) { // First check if it's for the full volume if (p.directory == null) { if (p.uuid == null) { // Should never happen - the Scoped Directory Access API does not allow it. Log.w(TAG, "ignoring entry whose uuid and directory is null"); continue; } grantedVolumes.add(p.uuid); } else { if (!ArrayUtils.contains(Environment.STANDARD_DIRECTORIES, p.directory)) { if (DEBUG) Log.v(TAG, "Ignoring non-standard directory on " + p); continue; } Set<String> dirs = grantedDirsByUuid.get(p.uuid); if (dirs == null) { // Life would be so much easier if Android had MultiMaps... dirs = new HashSet<>(1); grantedDirsByUuid.put(p.uuid, dirs); } dirs.add(p.directory); } } if (DEBUG) { Log.v(TAG, "grantedVolumes=" + grantedVolumes + ", grantedDirectories=" + grantedDirsByUuid); } // Add granted permissions to full volumes. grantedVolumes.forEach((uuid) -> permissions.add(new Object[] { packageName, uuid, /* dir= */ null, 1 })); // Add granted permissions to individual directories grantedDirsByUuid.forEach((uuid, dirs) -> { if (grantedVolumes.contains(uuid)) { Log.w(TAG, "Ignoring individual grants to " + uuid + ": " + dirs); } else { dirs.forEach((dir) -> permissions.add(new Object[] {packageName, uuid, dir, 1})); } }); } /** * Converts the permissions returned by AM to our own format. */ private List<Permission> parseGrantedPermissions(List<GrantedUriPermission> uriPermissions) { final List<Permission> permissions = new ArrayList<>(uriPermissions.size()); // TODO(b/72055774): we should query AUTHORITY_STORAGE or call DocumentsContract instead of // hardcoding the logic here. for (GrantedUriPermission uriPermission : uriPermissions) { final Uri uri = uriPermission.uri; final String authority = uri.getAuthority(); if (!Providers.AUTHORITY_STORAGE.equals(authority)) { Log.w(TAG, "Wrong authority on " + uri); continue; } final List<String> pathSegments = uri.getPathSegments(); if (pathSegments.size() < 2) { Log.w(TAG, "wrong path segments on " + uri); continue; } // TODO(b/72055774): make PATH_TREE private again if not used anymore if (!DocumentsContract.PATH_TREE.equals(pathSegments.get(0))) { Log.w(TAG, "wrong path tree on " + uri); continue; } final String[] uuidAndDir = pathSegments.get(1).split(":"); // uuid and dir are either UUID:DIR (for scoped directory) or UUID: (for full volume) if (uuidAndDir.length != 1 && uuidAndDir.length != 2) { Log.w(TAG, "could not parse uuid and directory on " + uri); continue; } final String uuid = Providers.ROOT_ID_DEVICE.equals(uuidAndDir[0]) ? null // primary : uuidAndDir[0]; // external volume final String dir = uuidAndDir.length == 1 ? null : uuidAndDir[1]; permissions .add(new Permission(uriPermission.packageName, uuid, dir, PERMISSION_GRANTED)); } return permissions; } @Override public String getType(Uri uri) { return null; Loading Loading @@ -246,9 +424,9 @@ public class ScopedAccessProvider extends ContentProvider { } pw.print("Permissions: "); final String[] selection = new String[packages.size()]; packages.toArray(selection); try (Cursor cursor = getPermissionsCursor(selection)) { for (int i = 0; i < packages.size(); i++) { final String pkg = packages.get(i); try (Cursor cursor = getPermissionsCursor(pkg)) { if (cursor == null) { pw.println("N/A"); } else { Loading @@ -265,6 +443,7 @@ public class ScopedAccessProvider extends ContentProvider { } } } } pw.print("Raw permissions: "); final List<Permission> rawPermissions = getAllPermissions(getContext()); Loading src/com/android/documentsui/prefs/ScopedAccessLocalPreferences.java +6 −1 Original line number Diff line number Diff line Loading @@ -54,11 +54,14 @@ public class ScopedAccessLocalPreferences { public static final int PERMISSION_ASK = 0; public static final int PERMISSION_ASK_AGAIN = 1; public static final int PERMISSION_NEVER_ASK = -1; // NOTE: this status is not used on preferences, but on permissions granted by AM public static final int PERMISSION_GRANTED = 2; @IntDef(flag = true, value = { PERMISSION_ASK, PERMISSION_ASK_AGAIN, PERMISSION_NEVER_ASK, PERMISSION_GRANTED }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionStatus {} Loading Loading @@ -181,6 +184,8 @@ public class ScopedAccessLocalPreferences { return "PERMISSION_ASK_AGAIN"; case PERMISSION_NEVER_ASK: return "PERMISSION_NEVER_ASK"; case PERMISSION_GRANTED: return "PERMISSION_GRANTED"; default: return "UNKNOWN"; } Loading Loading @@ -211,7 +216,7 @@ public class ScopedAccessLocalPreferences { public final String directory; public final int status; private Permission(String pkg, String uuid, String directory, Integer status) { public Permission(String pkg, String uuid, String directory, Integer status) { this.pkg = pkg; this.uuid = TextUtils.isEmpty(uuid) ? null : uuid; this.directory = directory; Loading Loading
src/com/android/documentsui/ScopedAccessActivity.java +4 −2 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.GrantedUriPermission; import android.content.ContentProviderClient; import android.content.Context; import android.content.DialogInterface; Loading Loading @@ -353,8 +354,9 @@ public class ScopedAccessActivity extends Activity { + " 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(); for (GrantedUriPermission uriPermission : am.getGrantedUriPermissions(packageName) .getList()) { final Uri uri = uriPermission.uri; if (uri == null) { Log.w(TAG, "null URI for " + uriPermission); continue; Loading
src/com/android/documentsui/ScopedAccessProvider.java +224 −45 Original line number Diff line number Diff line Loading @@ -27,26 +27,33 @@ import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABL import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_VOLUME_UUID; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.getInternalDirectoryName; import static com.android.documentsui.base.SharedMinimal.getExternalDirectoryName; import static com.android.documentsui.base.SharedMinimal.getInternalDirectoryName; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK_AGAIN; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_GRANTED; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_NEVER_ASK; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.getAllPackages; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.getAllPermissions; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.setScopedAccessPermissionStatus; import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.statusAsString; import static com.android.internal.util.Preconditions.checkArgument; import android.app.ActivityManager; import android.app.GrantedUriPermission; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Environment; import android.provider.DocumentsContract; import android.util.ArraySet; import android.util.Log; import com.android.documentsui.base.Providers; import com.android.documentsui.prefs.ScopedAccessLocalPreferences.Permission; import com.android.internal.util.ArrayUtils; Loading @@ -54,9 +61,11 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; //TODO(b/72055774): update javadoc once implementation is finished /** Loading Loading @@ -120,55 +129,122 @@ public class ScopedAccessProvider extends ContentProvider { case URI_PACKAGES: return getPackagesCursor(); case URI_PERMISSIONS: return getPermissionsCursor(selectionArgs); if (ArrayUtils.isEmpty(selectionArgs)) { throw new UnsupportedOperationException("selections cannot be empty"); } // For simplicity, we only support one package (which is what Settings is passing). if (selectionArgs.length > 1) { Log.w(TAG, "Using just first entry of " + Arrays.toString(selectionArgs)); } return getPermissionsCursor(selectionArgs[0]); default: throw new UnsupportedOperationException("Unsupported Uri " + uri); } } private Cursor getPackagesCursor() { // First get the packages that were denied final Set<String> pkgs = getAllPackages(getContext()); final Context context = getContext(); // First, get the packages that were denied final Set<String> pkgs = getAllPackages(context); // Second, query AM to get all packages that have a permission. final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); final List<GrantedUriPermission> amPkgs = am.getGrantedUriPermissions(null).getList(); if (!amPkgs.isEmpty()) { amPkgs.forEach((perm) -> pkgs.add(perm.packageName)); } if (ArrayUtils.isEmpty(pkgs)) { if (DEBUG) Log.v(TAG, "getPackagesCursor(): ignoring " + pkgs); if (DEBUG) Log.v(TAG, "getPackagesCursor(): nothing to do" ); return null; } // TODO(b/63720392): also need to query AM for granted permissions if (DEBUG) { Log.v(TAG, "getPackagesCursor(): denied=" + pkgs + ", granted=" + amPkgs); } // Then create the cursor // Finally, create the cursor final MatrixCursor cursor = new MatrixCursor(TABLE_PACKAGES_COLUMNS, pkgs.size()); pkgs.forEach((pkg) -> cursor.addRow( new Object[] { pkg })); return cursor; } private Cursor getPermissionsCursor(String[] packageNames) { // First get the packages that were denied final List<Permission> rawPermissions = getAllPermissions(getContext()); // TODO(b/63720392): need to unit tests to handle scenarios where the root permission of // a secondary volume mismatches a child permission (for example, child is allowed by root // is denied). private Cursor getPermissionsCursor(String packageName) { final Context context = getContext(); if (ArrayUtils.isEmpty(rawPermissions)) { if (DEBUG) Log.v(TAG, "getPermissionsCursor(): ignoring " + rawPermissions); return null; // List of volumes that were granted by AM at the root level - in that case, // we can ignored individual grants from AM or denials from our preferences final Set<String> grantedVolumes = new ArraySet<>(); // List of directories (mapped by volume uuid) that were granted by AM so they can be // ignored if also found on our preferences final Map<String, Set<String>> grantedDirsByUuid = new HashMap<>(); // Cursor rows final List<Object[]> permissions = new ArrayList<>(); // First, query AM to get all packages that have a permission. final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); final List<GrantedUriPermission> uriPermissions = am.getGrantedUriPermissions(packageName).getList(); if (DEBUG) { Log.v(TAG, "am returned =" + uriPermissions); } setGrantedPermissions(packageName, uriPermissions, permissions, grantedVolumes, grantedDirsByUuid); // TODO(b/72055774): unit tests for filters (permissions and/or package name); final List<Object[]> permissions = rawPermissions.stream() .filter(permission -> ArrayUtils.contains(packageNames, permission.pkg) && permission.status == PERMISSION_NEVER_ASK) .map(permission -> new Object[] { permission.pkg, permission.uuid, getExternalDirectoryName(permission.directory), Integer.valueOf(0) }) .collect(Collectors.toList()); // Now gets the packages that were denied final List<Permission> rawPermissions = getAllPermissions(context); // TODO(b/63720392): need to add logic to handle scenarios where the root permission of // a secondary volume mismatches a child permission (for example, child is allowed by root // is denied). if (DEBUG) { Log.v(TAG, "rawPermissions: " + rawPermissions); } // Merge the permissions granted by AM with the denied permissions saved on our preferences. for (Permission rawPermission : rawPermissions) { if (!packageName.equals(rawPermission.pkg)) { if (DEBUG) { Log.v(TAG, "ignoring " + rawPermission + " because package is not " + packageName); } continue; } if (rawPermission.status != PERMISSION_NEVER_ASK && rawPermission.status != PERMISSION_ASK_AGAIN) { // We only care for status where the user denied a request. if (DEBUG) { Log.v(TAG, "ignoring " + rawPermission + " because of its status"); } continue; } if (grantedVolumes.contains(rawPermission.uuid)) { if (DEBUG) { Log.v(TAG, "ignoring " + rawPermission + " because whole volume is granted"); } continue; } final Set<String> grantedDirs = grantedDirsByUuid.get(rawPermission.uuid); if (grantedDirs != null && grantedDirs.contains(rawPermission.directory)) { Log.w(TAG, "ignoring " + rawPermission + " because it was granted already"); continue; } permissions.add(new Object[] { packageName, rawPermission.uuid, getExternalDirectoryName(rawPermission.directory), 0 }); } // TODO(b/63720392): also need to query AM for granted permissions if (DEBUG) { Log.v(TAG, "total permissions: " + permissions.size()); } // Then create the cursor final MatrixCursor cursor = new MatrixCursor(TABLE_PERMISSIONS_COLUMNS, permissions.size()); Loading @@ -176,6 +252,108 @@ public class ScopedAccessProvider extends ContentProvider { return cursor; } /** * Converts the permissions returned by AM and add it to 3 buckets ({@code permissions}, * {@code grantedVolumes}, and {@code grantedDirsByUuid}). * * @param packageName name of package that the permissions were granted to. * @param uriPermissions permissions returend by AM * @param permissions list of permissions that can be converted to a {@link #TABLE_PERMISSIONS} * row. * @param grantedVolumes volume uuids that were granted full access. * @param grantedDirsByUuid directories that were granted individual acces (key is volume uuid, * value is list of directories). */ private void setGrantedPermissions(String packageName, List<GrantedUriPermission> uriPermissions, List<Object[]> permissions, Set<String> grantedVolumes, Map<String, Set<String>> grantedDirsByUuid) { final List<Permission> grantedPermissions = parseGrantedPermissions(uriPermissions); for (Permission p : grantedPermissions) { // First check if it's for the full volume if (p.directory == null) { if (p.uuid == null) { // Should never happen - the Scoped Directory Access API does not allow it. Log.w(TAG, "ignoring entry whose uuid and directory is null"); continue; } grantedVolumes.add(p.uuid); } else { if (!ArrayUtils.contains(Environment.STANDARD_DIRECTORIES, p.directory)) { if (DEBUG) Log.v(TAG, "Ignoring non-standard directory on " + p); continue; } Set<String> dirs = grantedDirsByUuid.get(p.uuid); if (dirs == null) { // Life would be so much easier if Android had MultiMaps... dirs = new HashSet<>(1); grantedDirsByUuid.put(p.uuid, dirs); } dirs.add(p.directory); } } if (DEBUG) { Log.v(TAG, "grantedVolumes=" + grantedVolumes + ", grantedDirectories=" + grantedDirsByUuid); } // Add granted permissions to full volumes. grantedVolumes.forEach((uuid) -> permissions.add(new Object[] { packageName, uuid, /* dir= */ null, 1 })); // Add granted permissions to individual directories grantedDirsByUuid.forEach((uuid, dirs) -> { if (grantedVolumes.contains(uuid)) { Log.w(TAG, "Ignoring individual grants to " + uuid + ": " + dirs); } else { dirs.forEach((dir) -> permissions.add(new Object[] {packageName, uuid, dir, 1})); } }); } /** * Converts the permissions returned by AM to our own format. */ private List<Permission> parseGrantedPermissions(List<GrantedUriPermission> uriPermissions) { final List<Permission> permissions = new ArrayList<>(uriPermissions.size()); // TODO(b/72055774): we should query AUTHORITY_STORAGE or call DocumentsContract instead of // hardcoding the logic here. for (GrantedUriPermission uriPermission : uriPermissions) { final Uri uri = uriPermission.uri; final String authority = uri.getAuthority(); if (!Providers.AUTHORITY_STORAGE.equals(authority)) { Log.w(TAG, "Wrong authority on " + uri); continue; } final List<String> pathSegments = uri.getPathSegments(); if (pathSegments.size() < 2) { Log.w(TAG, "wrong path segments on " + uri); continue; } // TODO(b/72055774): make PATH_TREE private again if not used anymore if (!DocumentsContract.PATH_TREE.equals(pathSegments.get(0))) { Log.w(TAG, "wrong path tree on " + uri); continue; } final String[] uuidAndDir = pathSegments.get(1).split(":"); // uuid and dir are either UUID:DIR (for scoped directory) or UUID: (for full volume) if (uuidAndDir.length != 1 && uuidAndDir.length != 2) { Log.w(TAG, "could not parse uuid and directory on " + uri); continue; } final String uuid = Providers.ROOT_ID_DEVICE.equals(uuidAndDir[0]) ? null // primary : uuidAndDir[0]; // external volume final String dir = uuidAndDir.length == 1 ? null : uuidAndDir[1]; permissions .add(new Permission(uriPermission.packageName, uuid, dir, PERMISSION_GRANTED)); } return permissions; } @Override public String getType(Uri uri) { return null; Loading Loading @@ -246,9 +424,9 @@ public class ScopedAccessProvider extends ContentProvider { } pw.print("Permissions: "); final String[] selection = new String[packages.size()]; packages.toArray(selection); try (Cursor cursor = getPermissionsCursor(selection)) { for (int i = 0; i < packages.size(); i++) { final String pkg = packages.get(i); try (Cursor cursor = getPermissionsCursor(pkg)) { if (cursor == null) { pw.println("N/A"); } else { Loading @@ -265,6 +443,7 @@ public class ScopedAccessProvider extends ContentProvider { } } } } pw.print("Raw permissions: "); final List<Permission> rawPermissions = getAllPermissions(getContext()); Loading
src/com/android/documentsui/prefs/ScopedAccessLocalPreferences.java +6 −1 Original line number Diff line number Diff line Loading @@ -54,11 +54,14 @@ public class ScopedAccessLocalPreferences { public static final int PERMISSION_ASK = 0; public static final int PERMISSION_ASK_AGAIN = 1; public static final int PERMISSION_NEVER_ASK = -1; // NOTE: this status is not used on preferences, but on permissions granted by AM public static final int PERMISSION_GRANTED = 2; @IntDef(flag = true, value = { PERMISSION_ASK, PERMISSION_ASK_AGAIN, PERMISSION_NEVER_ASK, PERMISSION_GRANTED }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionStatus {} Loading Loading @@ -181,6 +184,8 @@ public class ScopedAccessLocalPreferences { return "PERMISSION_ASK_AGAIN"; case PERMISSION_NEVER_ASK: return "PERMISSION_NEVER_ASK"; case PERMISSION_GRANTED: return "PERMISSION_GRANTED"; default: return "UNKNOWN"; } Loading Loading @@ -211,7 +216,7 @@ public class ScopedAccessLocalPreferences { public final String directory; public final int status; private Permission(String pkg, String uuid, String directory, Integer status) { public Permission(String pkg, String uuid, String directory, Integer status) { this.pkg = pkg; this.uuid = TextUtils.isEmpty(uuid) ? null : uuid; this.directory = directory; Loading