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