• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package com.android.externalstorage;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.usage.StorageStatsManager;
22 import android.content.AttributionSource;
23 import android.content.ContentResolver;
24 import android.content.UriPermission;
25 import android.database.Cursor;
26 import android.database.MatrixCursor;
27 import android.database.MatrixCursor.RowBuilder;
28 import android.net.Uri;
29 import android.os.Binder;
30 import android.os.Bundle;
31 import android.os.Environment;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.os.storage.DiskInfo;
35 import android.os.storage.StorageEventListener;
36 import android.os.storage.StorageManager;
37 import android.os.storage.VolumeInfo;
38 import android.provider.DocumentsContract;
39 import android.provider.DocumentsContract.Document;
40 import android.provider.DocumentsContract.Path;
41 import android.provider.DocumentsContract.Root;
42 import android.provider.Settings;
43 import android.system.ErrnoException;
44 import android.system.Os;
45 import android.system.OsConstants;
46 import android.text.TextUtils;
47 import android.util.ArrayMap;
48 import android.util.DebugUtils;
49 import android.util.Log;
50 import android.util.Pair;
51 
52 import com.android.internal.annotations.GuardedBy;
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.internal.content.FileSystemProvider;
55 import com.android.internal.util.IndentingPrintWriter;
56 
57 import java.io.File;
58 import java.io.FileDescriptor;
59 import java.io.FileNotFoundException;
60 import java.io.IOException;
61 import java.io.PrintWriter;
62 import java.util.Collections;
63 import java.util.List;
64 import java.util.Objects;
65 import java.util.UUID;
66 
67 public class ExternalStorageProvider extends FileSystemProvider {
68     private static final String TAG = "ExternalStorage";
69 
70     private static final boolean DEBUG = false;
71 
72     public static final String AUTHORITY = DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY;
73 
74     private static final Uri BASE_URI =
75             new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
76 
77     // docId format: root:path/to/file
78 
79     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
80             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
81             Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_QUERY_ARGS
82     };
83 
84     private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
85             Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
86             Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
87     };
88 
89     private static class RootInfo {
90         public String rootId;
91         public String volumeId;
92         public UUID storageUuid;
93         public int flags;
94         public String title;
95         public String docId;
96         public File visiblePath;
97         public File path;
98         // TODO (b/157033915): Make getFreeBytes() faster
99         public boolean reportAvailableBytes = false;
100     }
101 
102     private static final String ROOT_ID_PRIMARY_EMULATED =
103             DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
104 
105     private static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
106     private static final String GET_MEDIA_URI_CALL = "get_media_uri";
107 
108     private StorageManager mStorageManager;
109     private UserManager mUserManager;
110 
111     private final Object mRootsLock = new Object();
112 
113     @GuardedBy("mRootsLock")
114     private ArrayMap<String, RootInfo> mRoots = new ArrayMap<>();
115 
116     @Override
onCreate()117     public boolean onCreate() {
118         super.onCreate(DEFAULT_DOCUMENT_PROJECTION);
119 
120         mStorageManager = getContext().getSystemService(StorageManager.class);
121         mUserManager = getContext().getSystemService(UserManager.class);
122 
123         updateVolumes();
124 
125         mStorageManager.registerListener(new StorageEventListener() {
126                 @Override
127                 public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
128                     updateVolumes();
129                 }
130             });
131 
132         return true;
133     }
134 
enforceShellRestrictions()135     private void enforceShellRestrictions() {
136         if (UserHandle.getCallingAppId() == android.os.Process.SHELL_UID
137                 && mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
138             throw new SecurityException(
139                     "Shell user cannot access files for user " + UserHandle.myUserId());
140         }
141     }
142 
143     @Override
enforceReadPermissionInner(Uri uri, @NonNull AttributionSource attributionSource)144     protected int enforceReadPermissionInner(Uri uri,
145             @NonNull AttributionSource attributionSource) throws SecurityException {
146         enforceShellRestrictions();
147         return super.enforceReadPermissionInner(uri, attributionSource);
148     }
149 
150     @Override
enforceWritePermissionInner(Uri uri, @NonNull AttributionSource attributionSource)151     protected int enforceWritePermissionInner(Uri uri,
152             @NonNull AttributionSource attributionSource) throws SecurityException {
153         enforceShellRestrictions();
154         return super.enforceWritePermissionInner(uri, attributionSource);
155     }
156 
updateVolumes()157     public void updateVolumes() {
158         synchronized (mRootsLock) {
159             updateVolumesLocked();
160         }
161     }
162 
163     @GuardedBy("mRootsLock")
updateVolumesLocked()164     private void updateVolumesLocked() {
165         mRoots.clear();
166 
167         final int userId = UserHandle.myUserId();
168         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
169         for (VolumeInfo volume : volumes) {
170             if (!volume.isMountedReadable() || volume.getMountUserId() != userId) continue;
171 
172             final String rootId;
173             final String title;
174             final UUID storageUuid;
175             if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
176                 // We currently only support a single emulated volume per user mounted at
177                 // a time, and it's always considered the primary
178                 if (DEBUG) Log.d(TAG, "Found primary volume: " + volume);
179                 rootId = ROOT_ID_PRIMARY_EMULATED;
180 
181                 if (volume.isPrimaryEmulatedForUser(userId)) {
182                     // This is basically the user's primary device storage.
183                     // Use device name for the volume since this is likely same thing
184                     // the user sees when they mount their phone on another device.
185                     String deviceName = Settings.Global.getString(
186                             getContext().getContentResolver(), Settings.Global.DEVICE_NAME);
187 
188                     // Device name should always be set. In case it isn't, though,
189                     // fall back to a localized "Internal Storage" string.
190                     title = !TextUtils.isEmpty(deviceName)
191                             ? deviceName
192                             : getContext().getString(R.string.root_internal_storage);
193                     storageUuid = StorageManager.UUID_DEFAULT;
194                 } else {
195                     // This should cover all other storage devices, like an SD card
196                     // or USB OTG drive plugged in. Using getBestVolumeDescription()
197                     // will give us a nice string like "Samsung SD card" or "SanDisk USB drive"
198                     final VolumeInfo privateVol = mStorageManager.findPrivateForEmulated(volume);
199                     title = mStorageManager.getBestVolumeDescription(privateVol);
200                     storageUuid = StorageManager.convert(privateVol.fsUuid);
201                 }
202             } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC
203                     || volume.getType() == VolumeInfo.TYPE_STUB) {
204                 rootId = volume.getFsUuid();
205                 title = mStorageManager.getBestVolumeDescription(volume);
206                 storageUuid = null;
207             } else {
208                 // Unsupported volume; ignore
209                 continue;
210             }
211 
212             if (TextUtils.isEmpty(rootId)) {
213                 Log.d(TAG, "Missing UUID for " + volume.getId() + "; skipping");
214                 continue;
215             }
216             if (mRoots.containsKey(rootId)) {
217                 Log.w(TAG, "Duplicate UUID " + rootId + " for " + volume.getId() + "; skipping");
218                 continue;
219             }
220 
221             final RootInfo root = new RootInfo();
222             mRoots.put(rootId, root);
223 
224             root.rootId = rootId;
225             root.volumeId = volume.id;
226             root.storageUuid = storageUuid;
227             root.flags = Root.FLAG_LOCAL_ONLY
228                     | Root.FLAG_SUPPORTS_SEARCH
229                     | Root.FLAG_SUPPORTS_IS_CHILD;
230 
231             final DiskInfo disk = volume.getDisk();
232             if (DEBUG) Log.d(TAG, "Disk for root " + rootId + " is " + disk);
233             if (disk != null && disk.isSd()) {
234                 root.flags |= Root.FLAG_REMOVABLE_SD;
235             } else if (disk != null && disk.isUsb()) {
236                 root.flags |= Root.FLAG_REMOVABLE_USB;
237             }
238 
239             if (volume.getType() != VolumeInfo.TYPE_EMULATED) {
240                 root.flags |= Root.FLAG_SUPPORTS_EJECT;
241             }
242 
243             if (volume.isPrimary()) {
244                 root.flags |= Root.FLAG_ADVANCED;
245             }
246             // Dunno when this would NOT be the case, but never hurts to be correct.
247             if (volume.isMountedWritable()) {
248                 root.flags |= Root.FLAG_SUPPORTS_CREATE;
249             }
250             root.title = title;
251             if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
252                 root.flags |= Root.FLAG_HAS_SETTINGS;
253             }
254             if (volume.isVisibleForRead(userId)) {
255                 root.visiblePath = volume.getPathForUser(userId);
256             } else {
257                 root.visiblePath = null;
258             }
259             root.path = volume.getInternalPathForUser(userId);
260             try {
261                 root.docId = getDocIdForFile(root.path);
262             } catch (FileNotFoundException e) {
263                 throw new IllegalStateException(e);
264             }
265         }
266 
267         Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots");
268 
269         // Note this affects content://com.android.externalstorage.documents/root/39BD-07C5
270         // as well as content://com.android.externalstorage.documents/document/*/children,
271         // so just notify on content://com.android.externalstorage.documents/.
272         getContext().getContentResolver().notifyChange(BASE_URI, null, false);
273     }
274 
resolveRootProjection(String[] projection)275     private static String[] resolveRootProjection(String[] projection) {
276         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
277     }
278 
279     @Override
queryChildDocumentsForManage( String parentDocId, String[] projection, String sortOrder)280     public Cursor queryChildDocumentsForManage(
281             String parentDocId, String[] projection, String sortOrder)
282             throws FileNotFoundException {
283         return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
284     }
285 
286     /**
287      * Check that the directory is the root of storage or blocked file from tree.
288      *
289      * @param docId the docId of the directory to be checked
290      * @return true, should be blocked from tree. Otherwise, false.
291      */
292     @Override
shouldBlockFromTree(@onNull String docId)293     protected boolean shouldBlockFromTree(@NonNull String docId) {
294         try {
295             final File dir = getFileForDocId(docId, false /* visible */);
296 
297             // the file is null or it is not a directory
298             if (dir == null || !dir.isDirectory()) {
299                 return false;
300             }
301 
302             // Allow all directories on USB, including the root.
303             try {
304                 RootInfo rootInfo = getRootFromDocId(docId);
305                 if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) {
306                     return false;
307                 }
308             } catch (FileNotFoundException e) {
309                 Log.e(TAG, "Failed to determine rootInfo for docId");
310             }
311 
312             final String path = getPathFromDocId(docId);
313 
314             // Block the root of the storage
315             if (path.isEmpty()) {
316                 return true;
317             }
318 
319             // Block Download folder from tree
320             if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(),
321                     path.toLowerCase())) {
322                 return true;
323             }
324 
325             if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(),
326                     path.toLowerCase())) {
327                 return true;
328             }
329 
330             return false;
331         } catch (IOException e) {
332             throw new IllegalArgumentException(
333                     "Failed to determine if " + docId + " should block from tree " + ": " + e);
334         }
335     }
336 
337     @Override
getDocIdForFile(File file)338     protected String getDocIdForFile(File file) throws FileNotFoundException {
339         return getDocIdForFileMaybeCreate(file, false);
340     }
341 
getDocIdForFileMaybeCreate(File file, boolean createNewDir)342     private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
343             throws FileNotFoundException {
344         String path = file.getAbsolutePath();
345 
346         // Find the most-specific root path
347         boolean visiblePath = false;
348         RootInfo mostSpecificRoot = getMostSpecificRootForPath(path, false);
349 
350         if (mostSpecificRoot == null) {
351             // Try visible path if no internal path matches. MediaStore uses visible paths.
352             visiblePath = true;
353             mostSpecificRoot = getMostSpecificRootForPath(path, true);
354         }
355 
356         if (mostSpecificRoot == null) {
357             throw new FileNotFoundException("Failed to find root that contains " + path);
358         }
359 
360         // Start at first char of path under root
361         final String rootPath = visiblePath
362                 ? mostSpecificRoot.visiblePath.getAbsolutePath()
363                 : mostSpecificRoot.path.getAbsolutePath();
364         if (rootPath.equals(path)) {
365             path = "";
366         } else if (rootPath.endsWith("/")) {
367             path = path.substring(rootPath.length());
368         } else {
369             path = path.substring(rootPath.length() + 1);
370         }
371 
372         if (!file.exists() && createNewDir) {
373             Log.i(TAG, "Creating new directory " + file);
374             if (!file.mkdir()) {
375                 Log.e(TAG, "Could not create directory " + file);
376             }
377         }
378 
379         return mostSpecificRoot.rootId + ':' + path;
380     }
381 
getMostSpecificRootForPath(String path, boolean visible)382     private RootInfo getMostSpecificRootForPath(String path, boolean visible) {
383         // Find the most-specific root path
384         RootInfo mostSpecificRoot = null;
385         String mostSpecificPath = null;
386         synchronized (mRootsLock) {
387             for (int i = 0; i < mRoots.size(); i++) {
388                 final RootInfo root = mRoots.valueAt(i);
389                 final File rootFile = visible ? root.visiblePath : root.path;
390                 if (rootFile != null) {
391                     final String rootPath = rootFile.getAbsolutePath();
392                     if (path.startsWith(rootPath) && (mostSpecificPath == null
393                             || rootPath.length() > mostSpecificPath.length())) {
394                         mostSpecificRoot = root;
395                         mostSpecificPath = rootPath;
396                     }
397                 }
398             }
399         }
400 
401         return mostSpecificRoot;
402     }
403 
404     @Override
getFileForDocId(String docId, boolean visible)405     protected File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
406         return getFileForDocId(docId, visible, true);
407     }
408 
getFileForDocId(String docId, boolean visible, boolean mustExist)409     private File getFileForDocId(String docId, boolean visible, boolean mustExist)
410             throws FileNotFoundException {
411         RootInfo root = getRootFromDocId(docId);
412         return buildFile(root, docId, visible, mustExist);
413     }
414 
resolveDocId(String docId, boolean visible)415     private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
416             throws FileNotFoundException {
417         RootInfo root = getRootFromDocId(docId);
418         return Pair.create(root, buildFile(root, docId, visible, true));
419     }
420 
421     @VisibleForTesting
getPathFromDocId(String docId)422     static String getPathFromDocId(String docId) {
423         final int splitIndex = docId.indexOf(':', 1);
424         final String path = docId.substring(splitIndex + 1);
425 
426         if (path.isEmpty()) {
427             return path;
428         }
429 
430         // remove trailing "/"
431         if (path.charAt(path.length() - 1) == '/') {
432             return path.substring(0, path.length() - 1);
433         } else {
434             return path;
435         }
436     }
437 
getRootFromDocId(String docId)438     private RootInfo getRootFromDocId(String docId) throws FileNotFoundException {
439         final int splitIndex = docId.indexOf(':', 1);
440         final String tag = docId.substring(0, splitIndex);
441 
442         RootInfo root;
443         synchronized (mRootsLock) {
444             root = mRoots.get(tag);
445         }
446         if (root == null) {
447             throw new FileNotFoundException("No root for " + tag);
448         }
449 
450         return root;
451     }
452 
buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)453     private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
454             throws FileNotFoundException {
455         final int splitIndex = docId.indexOf(':', 1);
456         final String path = docId.substring(splitIndex + 1);
457 
458         File target = root.visiblePath != null ? root.visiblePath : root.path;
459         if (target == null) {
460             return null;
461         }
462         if (!target.exists()) {
463             target.mkdirs();
464         }
465         target = new File(target, path);
466         if (mustExist && !target.exists()) {
467             throw new FileNotFoundException("Missing file for " + docId + " at " + target);
468         }
469         return target;
470     }
471 
472     @Override
buildNotificationUri(String docId)473     protected Uri buildNotificationUri(String docId) {
474         return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId);
475     }
476 
477     @Override
onDocIdChanged(String docId)478     protected void onDocIdChanged(String docId) {
479         try {
480             // Touch the visible path to ensure that any sdcardfs caches have
481             // been updated to reflect underlying changes on disk.
482             final File visiblePath = getFileForDocId(docId, true, false);
483             if (visiblePath != null) {
484                 Os.access(visiblePath.getAbsolutePath(), OsConstants.F_OK);
485             }
486         } catch (FileNotFoundException | ErrnoException ignored) {
487         }
488     }
489 
490     @Override
onDocIdDeleted(String docId)491     protected void onDocIdDeleted(String docId) {
492         Uri uri = DocumentsContract.buildDocumentUri(AUTHORITY, docId);
493         getContext().revokeUriPermission(uri, ~0);
494     }
495 
496 
497     @Override
queryRoots(String[] projection)498     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
499         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
500         synchronized (mRootsLock) {
501             for (RootInfo root : mRoots.values()) {
502                 final RowBuilder row = result.newRow();
503                 row.add(Root.COLUMN_ROOT_ID, root.rootId);
504                 row.add(Root.COLUMN_FLAGS, root.flags);
505                 row.add(Root.COLUMN_TITLE, root.title);
506                 row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
507                 row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS);
508 
509                 long availableBytes = -1;
510                 if (root.reportAvailableBytes) {
511                     if (root.storageUuid != null) {
512                         try {
513                             availableBytes = getContext()
514                                     .getSystemService(StorageStatsManager.class)
515                                     .getFreeBytes(root.storageUuid);
516                         } catch (IOException e) {
517                             Log.w(TAG, e);
518                         }
519                     } else {
520                         availableBytes = root.path.getUsableSpace();
521                     }
522                 }
523                 row.add(Root.COLUMN_AVAILABLE_BYTES, availableBytes);
524             }
525         }
526         return result;
527     }
528 
529     @Override
findDocumentPath(@ullable String parentDocId, String childDocId)530     public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
531             throws FileNotFoundException {
532         final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
533         final RootInfo root = resolvedDocId.first;
534         File child = resolvedDocId.second;
535 
536         final File rootFile = root.visiblePath != null ? root.visiblePath
537                 : root.path;
538         final File parent = TextUtils.isEmpty(parentDocId)
539                 ? rootFile
540                 : getFileForDocId(parentDocId);
541 
542         return new Path(parentDocId == null ? root.rootId : null, findDocumentPath(parent, child));
543     }
544 
getDocumentUri(String path, List<UriPermission> accessUriPermissions)545     private Uri getDocumentUri(String path, List<UriPermission> accessUriPermissions)
546             throws FileNotFoundException {
547         File doc = new File(path);
548 
549         final String docId = getDocIdForFile(doc);
550 
551         UriPermission docUriPermission = null;
552         UriPermission treeUriPermission = null;
553         for (UriPermission uriPermission : accessUriPermissions) {
554             final Uri uri = uriPermission.getUri();
555             if (AUTHORITY.equals(uri.getAuthority())) {
556                 boolean matchesRequestedDoc = false;
557                 if (DocumentsContract.isTreeUri(uri)) {
558                     final String parentDocId = DocumentsContract.getTreeDocumentId(uri);
559                     if (isChildDocument(parentDocId, docId)) {
560                         treeUriPermission = uriPermission;
561                         matchesRequestedDoc = true;
562                     }
563                 } else {
564                     final String candidateDocId = DocumentsContract.getDocumentId(uri);
565                     if (Objects.equals(docId, candidateDocId)) {
566                         docUriPermission = uriPermission;
567                         matchesRequestedDoc = true;
568                     }
569                 }
570 
571                 if (matchesRequestedDoc && allowsBothReadAndWrite(uriPermission)) {
572                     // This URI permission provides everything an app can get, no need to
573                     // further check any other granted URI.
574                     break;
575                 }
576             }
577         }
578 
579         // Full permission URI first.
580         if (allowsBothReadAndWrite(treeUriPermission)) {
581             return DocumentsContract.buildDocumentUriUsingTree(treeUriPermission.getUri(), docId);
582         }
583 
584         if (allowsBothReadAndWrite(docUriPermission)) {
585             return docUriPermission.getUri();
586         }
587 
588         // Then partial permission URI.
589         if (treeUriPermission != null) {
590             return DocumentsContract.buildDocumentUriUsingTree(treeUriPermission.getUri(), docId);
591         }
592 
593         if (docUriPermission != null) {
594             return docUriPermission.getUri();
595         }
596 
597         throw new SecurityException("The app is not given any access to the document under path " +
598                 path + " with permissions granted in " + accessUriPermissions);
599     }
600 
allowsBothReadAndWrite(UriPermission permission)601     private static boolean allowsBothReadAndWrite(UriPermission permission) {
602         return permission != null
603                 && permission.isReadPermission()
604                 && permission.isWritePermission();
605     }
606 
607     @Override
querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)608     public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
609             throws FileNotFoundException {
610         final File parent;
611 
612         synchronized (mRootsLock) {
613             RootInfo root = mRoots.get(rootId);
614             parent = root.visiblePath != null ? root.visiblePath
615                 : root.path;
616         }
617 
618         return querySearchDocuments(parent, projection, Collections.emptySet(), queryArgs);
619     }
620 
621     @Override
ejectRoot(String rootId)622     public void ejectRoot(String rootId) {
623         final long token = Binder.clearCallingIdentity();
624         RootInfo root = mRoots.get(rootId);
625         if (root != null) {
626             try {
627                 mStorageManager.unmount(root.volumeId);
628             } catch (RuntimeException e) {
629                 throw new IllegalStateException(e);
630             } finally {
631                 Binder.restoreCallingIdentity(token);
632             }
633         }
634     }
635 
636     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)637     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
638         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
639         synchronized (mRootsLock) {
640             for (int i = 0; i < mRoots.size(); i++) {
641                 final RootInfo root = mRoots.valueAt(i);
642                 pw.println("Root{" + root.rootId + "}:");
643                 pw.increaseIndent();
644                 pw.printPair("flags", DebugUtils.flagsToString(Root.class, "FLAG_", root.flags));
645                 pw.println();
646                 pw.printPair("title", root.title);
647                 pw.printPair("docId", root.docId);
648                 pw.println();
649                 pw.printPair("path", root.path);
650                 pw.printPair("visiblePath", root.visiblePath);
651                 pw.decreaseIndent();
652                 pw.println();
653             }
654         }
655     }
656 
657     @Override
call(String method, String arg, Bundle extras)658     public Bundle call(String method, String arg, Bundle extras) {
659         Bundle bundle = super.call(method, arg, extras);
660         if (bundle == null && !TextUtils.isEmpty(method)) {
661             switch (method) {
662                 case "getDocIdForFileCreateNewDir": {
663                     getContext().enforceCallingPermission(
664                             android.Manifest.permission.MANAGE_DOCUMENTS, null);
665                     if (TextUtils.isEmpty(arg)) {
666                         return null;
667                     }
668                     try {
669                         final String docId = getDocIdForFileMaybeCreate(new File(arg), true);
670                         bundle = new Bundle();
671                         bundle.putString("DOC_ID", docId);
672                     } catch (FileNotFoundException e) {
673                         Log.w(TAG, "file '" + arg + "' not found");
674                         return null;
675                     }
676                     break;
677                 }
678                 case GET_DOCUMENT_URI_CALL: {
679                     // All callers must go through MediaProvider
680                     getContext().enforceCallingPermission(
681                             android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
682 
683                     final Uri fileUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
684                     final List<UriPermission> accessUriPermissions = extras
685                             .getParcelableArrayList(DocumentsContract.EXTRA_URI_PERMISSIONS);
686 
687                     final String path = fileUri.getPath();
688                     try {
689                         final Bundle out = new Bundle();
690                         final Uri uri = getDocumentUri(path, accessUriPermissions);
691                         out.putParcelable(DocumentsContract.EXTRA_URI, uri);
692                         return out;
693                     } catch (FileNotFoundException e) {
694                         throw new IllegalStateException("File in " + path + " is not found.", e);
695                     }
696                 }
697                 case GET_MEDIA_URI_CALL: {
698                     // All callers must go through MediaProvider
699                     getContext().enforceCallingPermission(
700                             android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
701 
702                     final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
703                     final String docId = DocumentsContract.getDocumentId(documentUri);
704                     try {
705                         final Bundle out = new Bundle();
706                         final Uri uri = Uri.fromFile(getFileForDocId(docId, true));
707                         out.putParcelable(DocumentsContract.EXTRA_URI, uri);
708                         return out;
709                     } catch (FileNotFoundException e) {
710                         throw new IllegalStateException(e);
711                     }
712                 }
713                 default:
714                     Log.w(TAG, "unknown method passed to call(): " + method);
715             }
716         }
717         return bundle;
718     }
719 }
720