• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.documentsui;
17 
18 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.COL_GRANTED;
19 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES;
20 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COLUMNS;
21 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PACKAGES_COL_PACKAGE;
22 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS;
23 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COLUMNS;
24 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_DIRECTORY;
25 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_GRANTED;
26 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_PACKAGE;
27 import static android.os.storage.StorageVolume.ScopedAccessProviderContract.TABLE_PERMISSIONS_COL_VOLUME_UUID;
28 import static android.os.Environment.isStandardDirectory;
29 
30 import static com.android.documentsui.base.SharedMinimal.DEBUG;
31 import static com.android.documentsui.base.SharedMinimal.getExternalDirectoryName;
32 import static com.android.documentsui.base.SharedMinimal.getInternalDirectoryName;
33 import static com.android.documentsui.base.SharedMinimal.getUriPermission;
34 import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_ASK_AGAIN;
35 import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_GRANTED;
36 import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.PERMISSION_NEVER_ASK;
37 import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.clearScopedAccessPreferences;
38 import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.getAllPackages;
39 import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.getAllPermissions;
40 import static com.android.documentsui.prefs.ScopedAccessLocalPreferences.setScopedAccessPermissionStatus;
41 import static com.android.internal.util.Preconditions.checkArgument;
42 
43 import android.annotation.Nullable;
44 import android.app.ActivityManager;
45 import android.app.GrantedUriPermission;
46 import android.content.ContentProvider;
47 import android.content.ContentProviderClient;
48 import android.content.ContentResolver;
49 import android.content.ContentValues;
50 import android.content.Context;
51 import android.content.Intent;
52 import android.content.UriMatcher;
53 import android.database.Cursor;
54 import android.database.MatrixCursor;
55 import android.net.Uri;
56 import android.os.Environment;
57 import android.os.UserHandle;
58 import android.os.storage.StorageManager;
59 import android.os.storage.StorageVolume;
60 import android.provider.DocumentsContract;
61 import android.util.ArraySet;
62 import android.util.Log;
63 
64 import com.android.documentsui.base.Providers;
65 import com.android.documentsui.prefs.ScopedAccessLocalPreferences.Permission;
66 import com.android.internal.util.ArrayUtils;
67 
68 import java.io.FileDescriptor;
69 import java.io.PrintWriter;
70 import java.util.ArrayList;
71 import java.util.Arrays;
72 import java.util.HashMap;
73 import java.util.HashSet;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Set;
77 
78 //TODO(b/72055774): update javadoc once implementation is finished
79 /**
80  * Provider used to manage scoped access directory permissions.
81  *
82  * <p>It fetches data from 2 sources:
83  *
84  * <ul>
85  * <li>{@link com.android.documentsui.prefs.ScopedAccessLocalPreferences} for denied permissions.
86  * <li>{@link ActivityManager} for allowed permissions.
87  * </ul>
88  *
89  * <p>And returns the results in 2 tables:
90  *
91  * <ul>
92  * <li>{@link #TABLE_PACKAGES}: read-only table with the name of all packages
93  * (column ({@link android.os.storage.StorageVolume.ScopedAccessProviderContract#COL_PACKAGE}) that
94  * had a scoped access directory permission granted or denied.
95  * <li>{@link #TABLE_PERMISSIONS}: writable table with the name of all packages
96  * (column ({@link android.os.storage.StorageVolume.ScopedAccessProviderContract#COL_PACKAGE}) that
97  * had a scoped access directory
98  * (column ({@link android.os.storage.StorageVolume.ScopedAccessProviderContract#COL_DIRECTORY})
99  * permission for a volume (column
100  * {@link android.os.storage.StorageVolume.ScopedAccessProviderContract#COL_VOLUME_UUID}, which
101  * contains the volume UUID or {@code null} if it's the primary partition) granted or denied
102  * (column ({@link android.os.storage.StorageVolume.ScopedAccessProviderContract#COL_GRANTED}).
103  * </ul>
104  *
105  * <p><b>Note:</b> the {@code query()} methods return all entries; it does not support selection or
106  * projections.
107  */
108 // TODO(b/72055774): add unit tests
109 public class ScopedAccessProvider extends ContentProvider {
110 
111     private static final String TAG = "ScopedAccessProvider";
112     private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
113 
114     private static final int URI_PACKAGES = 1;
115     private static final int URI_PERMISSIONS = 2;
116 
117     public static final String AUTHORITY = "com.android.documentsui.scopedAccess";
118 
119     static {
sMatcher.addURI(AUTHORITY, TABLE_PACKAGES + "/*", URI_PACKAGES)120         sMatcher.addURI(AUTHORITY, TABLE_PACKAGES + "/*", URI_PACKAGES);
sMatcher.addURI(AUTHORITY, TABLE_PERMISSIONS + "/*", URI_PERMISSIONS)121         sMatcher.addURI(AUTHORITY, TABLE_PERMISSIONS + "/*", URI_PERMISSIONS);
122     }
123 
124     @Override
onCreate()125     public boolean onCreate() {
126         return true;
127     }
128 
129     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)130     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
131             String sortOrder) {
132         if (DEBUG) {
133             Log.v(TAG, "query(" + uri + "): proj=" + Arrays.toString(projection)
134                 + ", sel=" + selection);
135         }
136         switch (sMatcher.match(uri)) {
137             case URI_PACKAGES:
138                 return getPackagesCursor();
139             case URI_PERMISSIONS:
140                 if (ArrayUtils.isEmpty(selectionArgs)) {
141                     throw new UnsupportedOperationException("selections cannot be empty");
142                 }
143                 // For simplicity, we only support one package (which is what Settings is passing).
144                 if (selectionArgs.length > 1) {
145                     Log.w(TAG, "Using just first entry of " + Arrays.toString(selectionArgs));
146                 }
147                 return getPermissionsCursor(selectionArgs[0]);
148             default:
149                 throw new UnsupportedOperationException("Unsupported Uri " + uri);
150         }
151     }
152 
getPackagesCursor()153     private Cursor getPackagesCursor() {
154         final Context context = getContext();
155 
156         // First, get the packages that were denied
157         final Set<String> pkgs = getAllPackages(context);
158 
159         // Second, query AM to get all packages that have a permission.
160         final ActivityManager am =
161                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
162 
163         final List<GrantedUriPermission> amPkgs = am.getGrantedUriPermissions(null).getList();
164         if (!amPkgs.isEmpty()) {
165             amPkgs.forEach((perm) -> pkgs.add(perm.packageName));
166         }
167 
168         if (ArrayUtils.isEmpty(pkgs)) {
169             if (DEBUG) Log.v(TAG, "getPackagesCursor(): nothing to do" );
170             return null;
171         }
172 
173         if (DEBUG) {
174             Log.v(TAG, "getPackagesCursor(): denied=" + pkgs + ", granted=" + amPkgs);
175         }
176 
177         // Finally, create the cursor
178         final MatrixCursor cursor = new MatrixCursor(TABLE_PACKAGES_COLUMNS, pkgs.size());
179         pkgs.forEach((pkg) -> cursor.addRow( new Object[] { pkg }));
180         return cursor;
181     }
182 
183     // TODO(b/72055774): need to unit tests to handle scenarios where the root permission of
184     // a secondary volume mismatches a child permission (for example, child is allowed by root
185     // is denied).
getPermissionsCursor(String packageName)186     private Cursor getPermissionsCursor(String packageName) {
187         final Context context = getContext();
188 
189         // List of volumes that were granted by AM at the root level - in that case,
190         // we can ignored individual grants from AM or denials from our preferences
191         final Set<String> grantedVolumes = new ArraySet<>();
192 
193         // List of directories (mapped by volume uuid) that were granted by AM so they can be
194         // ignored if also found on our preferences
195         final Map<String, Set<String>> grantedDirsByUuid = new HashMap<>();
196 
197         // Cursor rows
198         final List<Object[]> permissions = new ArrayList<>();
199 
200         // First, query AM to get all packages that have a permission.
201         final ActivityManager am =
202                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
203         final List<GrantedUriPermission> uriPermissions =
204                 am.getGrantedUriPermissions(packageName).getList();
205         if (DEBUG) {
206             Log.v(TAG, "am returned =" + uriPermissions);
207         }
208         setGrantedPermissions(packageName, uriPermissions, permissions, grantedVolumes,
209                 grantedDirsByUuid);
210 
211         // Now  gets the packages that were denied
212         final List<Permission> rawPermissions = getAllPermissions(context);
213 
214         if (DEBUG) {
215             Log.v(TAG, "rawPermissions: " + rawPermissions);
216         }
217 
218         // Merge the permissions granted by AM with the denied permissions saved on our preferences.
219         for (Permission rawPermission : rawPermissions) {
220             if (!packageName.equals(rawPermission.pkg)) {
221                 if (DEBUG) {
222                     Log.v(TAG,
223                             "ignoring " + rawPermission + " because package is not " + packageName);
224                 }
225                 continue;
226             }
227             if (rawPermission.status != PERMISSION_NEVER_ASK
228                     && rawPermission.status != PERMISSION_ASK_AGAIN) {
229                 // We only care for status where the user denied a request.
230                 if (DEBUG) {
231                     Log.v(TAG, "ignoring " + rawPermission + " because of its status");
232                 }
233                 continue;
234             }
235             if (grantedVolumes.contains(rawPermission.uuid)) {
236                 if (DEBUG) {
237                     Log.v(TAG, "ignoring " + rawPermission + " because whole volume is granted");
238                 }
239                 continue;
240             }
241             final Set<String> grantedDirs = grantedDirsByUuid.get(rawPermission.uuid);
242             if (grantedDirs != null
243                     && grantedDirs.contains(rawPermission.directory)) {
244                 Log.w(TAG, "ignoring " + rawPermission + " because it was granted already");
245                 continue;
246             }
247             permissions.add(new Object[] {
248                     packageName, rawPermission.uuid,
249                     getExternalDirectoryName(rawPermission.directory), 0
250             });
251         }
252 
253         if (DEBUG) {
254             Log.v(TAG, "total permissions: " + permissions.size());
255         }
256 
257         // Then create the cursor
258         final MatrixCursor cursor = new MatrixCursor(TABLE_PERMISSIONS_COLUMNS, permissions.size());
259         permissions.forEach((row) -> cursor.addRow(row));
260         return cursor;
261     }
262 
263     /**
264      * Converts the permissions returned by AM and add it to 3 buckets ({@code permissions},
265      * {@code grantedVolumes}, and {@code grantedDirsByUuid}).
266      *
267      * @param packageName name of package that the permissions were granted to.
268      * @param uriPermissions permissions returend by AM
269      * @param permissions list of permissions that can be converted to a {@link #TABLE_PERMISSIONS}
270      * row.
271      * @param grantedVolumes volume uuids that were granted full access.
272      * @param grantedDirsByUuid directories that were granted individual acces (key is volume uuid,
273      * value is list of directories).
274      */
setGrantedPermissions(String packageName, List<GrantedUriPermission> uriPermissions, List<Object[]> permissions, Set<String> grantedVolumes, Map<String, Set<String>> grantedDirsByUuid)275     private void setGrantedPermissions(String packageName, List<GrantedUriPermission> uriPermissions,
276             List<Object[]> permissions, Set<String> grantedVolumes,
277             Map<String, Set<String>> grantedDirsByUuid) {
278         final List<Permission> grantedPermissions = parseGrantedPermissions(uriPermissions);
279 
280         for (Permission p : grantedPermissions) {
281             // First check if it's for the full volume
282             if (p.directory == null) {
283                 if (p.uuid == null) {
284                     // Should never happen - the Scoped Directory Access API does not allow it.
285                     Log.w(TAG, "ignoring entry whose uuid and directory is null");
286                     continue;
287                 }
288                 grantedVolumes.add(p.uuid);
289             } else {
290                 if (!ArrayUtils.contains(Environment.STANDARD_DIRECTORIES, p.directory)) {
291                     if (DEBUG) Log.v(TAG, "Ignoring non-standard directory on " + p);
292                     continue;
293                 }
294 
295                 Set<String> dirs = grantedDirsByUuid.get(p.uuid);
296                 if (dirs == null) {
297                     // Life would be so much easier if Android had MultiMaps...
298                     dirs = new HashSet<>(1);
299                     grantedDirsByUuid.put(p.uuid, dirs);
300                 }
301                 dirs.add(p.directory);
302             }
303         }
304 
305         if (DEBUG) {
306             Log.v(TAG, "grantedVolumes=" + grantedVolumes
307                     + ", grantedDirectories=" + grantedDirsByUuid);
308         }
309         // Add granted permissions to full volumes.
310         grantedVolumes.forEach((uuid) -> permissions.add(new Object[] {
311                 packageName, uuid, /* dir= */ null, 1
312         }));
313 
314         // Add granted permissions to individual directories
315         grantedDirsByUuid.forEach((uuid, dirs) -> {
316             if (grantedVolumes.contains(uuid)) {
317                 Log.w(TAG, "Ignoring individual grants to " + uuid + ": " + dirs);
318             } else {
319                 dirs.forEach((dir) -> permissions.add(new Object[] {packageName, uuid, dir, 1}));
320             }
321         });
322     }
323 
324     /**
325      * Converts the permissions returned by AM to our own format.
326      */
parseGrantedPermissions(List<GrantedUriPermission> uriPermissions)327     private List<Permission> parseGrantedPermissions(List<GrantedUriPermission> uriPermissions) {
328         final List<Permission> permissions = new ArrayList<>(uriPermissions.size());
329         // TODO(b/72055774): we should query AUTHORITY_STORAGE or call DocumentsContract instead of
330         // hardcoding the logic here.
331         for (GrantedUriPermission uriPermission : uriPermissions) {
332             final Uri uri = uriPermission.uri;
333             final String authority = uri.getAuthority();
334             if (!Providers.AUTHORITY_STORAGE.equals(authority)) {
335                 Log.w(TAG, "Wrong authority on " + uri);
336                 continue;
337             }
338             final List<String> pathSegments = uri.getPathSegments();
339             if (pathSegments.size() < 2) {
340                 Log.w(TAG, "wrong path segments on " + uri);
341                 continue;
342             }
343             // TODO(b/72055774): make PATH_TREE private again if not used anymore
344             if (!DocumentsContract.PATH_TREE.equals(pathSegments.get(0))) {
345                 Log.w(TAG, "wrong path tree on " + uri);
346                 continue;
347             }
348 
349             final String[] uuidAndDir = pathSegments.get(1).split(":");
350             // uuid and dir are either UUID:DIR (for scoped directory) or UUID: (for full volume)
351             if (uuidAndDir.length != 1 && uuidAndDir.length != 2) {
352                 Log.w(TAG, "could not parse uuid and directory on " + uri);
353                 continue;
354             }
355             // TODO(b/72055774): to make things uglier, the Documents directory in the primary
356             // storage is a special case as its URI is "$ROOT_ID_HOME", instead of
357             // "${ROOT_ID_DEVICE}/Documents. This is another reason to move this logic to the
358             // provider...
359             final String uuid, dir;
360             if (Providers.ROOT_ID_HOME.equals(uuidAndDir[0])) {
361                 uuid = null;
362                 dir = Environment.DIRECTORY_DOCUMENTS;
363             } else {
364                 uuid = Providers.ROOT_ID_DEVICE.equals(uuidAndDir[0])
365                         ? null // primary
366                         : uuidAndDir[0]; // external volume
367                 dir = uuidAndDir.length == 1 ? null : uuidAndDir[1];
368             }
369             permissions
370                     .add(new Permission(uriPermission.packageName, uuid, dir, PERMISSION_GRANTED));
371         }
372         return permissions;
373     }
374 
375     @Override
getType(Uri uri)376     public String getType(Uri uri) {
377         return null;
378     }
379 
380     @Override
insert(Uri uri, ContentValues values)381     public Uri insert(Uri uri, ContentValues values) {
382         throw new UnsupportedOperationException("insert(): unsupported " + uri);
383     }
384 
385     @Override
delete(Uri uri, String selection, String[] selectionArgs)386     public int delete(Uri uri, String selection, String[] selectionArgs) {
387         if (sMatcher.match(uri) != URI_PERMISSIONS) {
388             throw new UnsupportedOperationException("delete(): unsupported " + uri);
389         }
390 
391         if (DEBUG) {
392             Log.v(TAG, "delete(" + uri + "): " + Arrays.toString(selectionArgs));
393         }
394 
395         // TODO(b/72055774): add unit tests for invalid input
396         checkArgument(selectionArgs != null && selectionArgs.length == 1,
397                 "Must have exactly 1 args: package_name" + Arrays.toString(selectionArgs));
398         final String packageName = selectionArgs[0];
399 
400         // Delete just our preferences - the URI permissions is handled externally
401         // TODO(b/72055774): move logic to revoke permissions here, so AppStorageSettings does
402         // not need to call am.clearGrantedUriPermissions(packageName) (then we could remove that
403         // method from ActivityManager)
404         return clearScopedAccessPreferences(getContext(), packageName);
405     }
406 
407     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)408     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
409         if (sMatcher.match(uri) != URI_PERMISSIONS) {
410             throw new UnsupportedOperationException("update(): unsupported " + uri);
411         }
412 
413         if (DEBUG) {
414             Log.v(TAG, "update(" + uri + "): " + Arrays.toString(selectionArgs) + " = " + values);
415         }
416 
417         // TODO(b/72055774): add unit tests for invalid input
418         checkArgument(selectionArgs != null && selectionArgs.length == 3,
419                 "Must have exactly 3 args: package_name, (nullable) uuid, (nullable) directory: "
420                         + Arrays.toString(selectionArgs));
421         final String packageName = selectionArgs[0];
422         final String uuid = selectionArgs[1];
423         final String dir = selectionArgs[2];
424         final boolean granted = values.getAsBoolean(COL_GRANTED);
425 
426         // First update the effective URI permission ...
427         if (!persistUriPermission(packageName, uuid, dir, granted)) {
428             // Failed - nothing left to do...
429             return 0;
430         }
431 
432         // ...then our preferences.
433         setScopedAccessPermissionStatus(getContext(), packageName, uuid,
434                 getInternalDirectoryName(dir), granted ? PERMISSION_GRANTED : PERMISSION_NEVER_ASK);
435         return 1;
436     }
437 
438     /**
439      * Calls AM to persist a URI.
440      *
441      * @return whether the call succeeded.
442      */
persistUriPermission(String packageName, @Nullable String uuid, @Nullable String directory, boolean granted)443     private boolean persistUriPermission(String packageName, @Nullable String uuid,
444             @Nullable String directory, boolean granted) {
445         final Context context = getContext();
446 
447         final ContentProviderClient storageClient = context.getContentResolver()
448                 .acquireContentProviderClient(Providers.AUTHORITY_STORAGE);
449 
450         final StorageManager sm = context.getSystemService(StorageManager.class);
451 
452         StorageVolume volume = null;
453         if (uuid == null) {
454             if (directory == null) {
455                 Log.w(TAG, "cannot grant full access to the primary volume");
456                 return false;
457             }
458             volume = sm.getPrimaryStorageVolume();
459         } else {
460             for (StorageVolume candidate : sm.getVolumeList()) {
461                 if (uuid.equals(candidate.getUuid())) {
462                     volume = candidate;
463                     break;
464                 }
465             }
466             if (volume == null) {
467                 Log.w(TAG, "didn't find volume for UUID=" + uuid);
468                 return false;
469             }
470             if (directory != null && !isStandardDirectory(directory)) {
471                 Log.w(TAG, "not a scoped directory: " + directory);
472                 return false;
473             }
474         }
475 
476         return getUriPermission(context, storageClient, volume, getInternalDirectoryName(directory),
477                 UserHandle.getCallingUserId(), /* logMetrics= */ false,
478                 (file, volumeLabel, isRoot, isPrimary, grantedUri, rootUri) -> {
479                     updatePermission(context, grantedUri, packageName, granted);
480                     return true;
481                 });
482     }
483 
updatePermission(Context context, Uri grantedUri, String toPackage, boolean granted)484     private void updatePermission(Context context, Uri grantedUri, String toPackage,
485             boolean granted) {
486         final int persistFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
487                 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
488         final int grantFlags = persistFlags
489                 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
490                 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
491 
492         final ContentResolver cr = context.getContentResolver();
493         if (granted) {
494             context.grantUriPermission(toPackage, grantedUri, grantFlags);
495             cr.takePersistableUriPermission(toPackage, grantedUri, persistFlags);
496         } else {
497             context.revokeUriPermission(grantedUri, grantFlags);
498             // There's no need to release after revoking
499         }
500     }
501 
502     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)503     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
504         final String prefix = "  ";
505 
506         final List<String> packages = new ArrayList<>();
507         pw.print("Packages: ");
508         try (Cursor cursor = getPackagesCursor()) {
509             if (cursor == null || cursor.getCount() == 0) {
510                 pw.println("N/A");
511             } else {
512                 pw.println(cursor.getCount());
513                 while (cursor.moveToNext()) {
514                     final String pkg = cursor.getString(TABLE_PACKAGES_COL_PACKAGE);
515                     packages.add(pkg);
516                     pw.print(prefix);
517                     pw.println(pkg);
518                 }
519             }
520         }
521 
522         pw.print("Permissions: ");
523         for (int i = 0; i < packages.size(); i++) {
524             final String pkg = packages.get(i);
525             try (Cursor cursor = getPermissionsCursor(pkg)) {
526                 if (cursor == null) {
527                     pw.println("N/A");
528                 } else {
529                     pw.println(cursor.getCount());
530                     while (cursor.moveToNext()) {
531                         pw.print(prefix); pw.print(cursor.getString(TABLE_PERMISSIONS_COL_PACKAGE));
532                         pw.print('/');
533                         final String uuid = cursor.getString(TABLE_PERMISSIONS_COL_VOLUME_UUID);
534                         if (uuid != null) {
535                             pw.print(uuid); pw.print('>');
536                         }
537                         pw.print(cursor.getString(TABLE_PERMISSIONS_COL_DIRECTORY));
538                         pw.print(": "); pw.println(cursor.getInt(TABLE_PERMISSIONS_COL_GRANTED) == 1);
539                     }
540                 }
541             }
542         }
543 
544         pw.print("Raw permissions: ");
545         final List<Permission> rawPermissions = getAllPermissions(getContext());
546         if (rawPermissions.isEmpty()) {
547             pw.println("N/A");
548         } else {
549             final int size = rawPermissions.size();
550             pw.println(size);
551             for (int i = 0; i < size; i++) {
552                 final Permission permission = rawPermissions.get(i);
553                 pw.print(prefix); pw.println(permission);
554             }
555         }
556     }
557 }
558