Environment.java revision 63d0a067997cecf9c6e97a17852f9b657bbba48e
1/* 2 * Copyright (C) 2007 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 17package android.os; 18 19import android.os.storage.IMountService; 20import android.os.storage.StorageManager; 21import android.os.storage.StorageVolume; 22import android.text.TextUtils; 23import android.util.Log; 24 25import com.android.internal.annotations.GuardedBy; 26 27import java.io.File; 28import java.io.IOException; 29 30/** 31 * Provides access to environment variables. 32 */ 33public class Environment { 34 private static final String TAG = "Environment"; 35 36 private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"; 37 private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE"; 38 private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET"; 39 private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE"; 40 private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT"; 41 42 /** {@hide} */ 43 public static String DIRECTORY_ANDROID = "Android"; 44 45 private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system"); 46 private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media"); 47 48 private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull( 49 ENV_EMULATED_STORAGE_TARGET); 50 51 private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; 52 53 private static UserEnvironment sCurrentUser; 54 55 private static final Object sLock = new Object(); 56 57 @GuardedBy("sLock") 58 private static volatile StorageVolume sPrimaryVolume; 59 60 private static StorageVolume getPrimaryVolume() { 61 if (sPrimaryVolume == null) { 62 synchronized (sLock) { 63 if (sPrimaryVolume == null) { 64 try { 65 IMountService mountService = IMountService.Stub.asInterface(ServiceManager 66 .getService("mount")); 67 final StorageVolume[] volumes = mountService.getVolumeList(); 68 sPrimaryVolume = StorageManager.getPrimaryVolume(volumes); 69 } catch (Exception e) { 70 Log.e(TAG, "couldn't talk to MountService", e); 71 } 72 } 73 } 74 } 75 return sPrimaryVolume; 76 } 77 78 static { 79 initForCurrentUser(); 80 } 81 82 /** {@hide} */ 83 public static void initForCurrentUser() { 84 final int userId = UserHandle.myUserId(); 85 sCurrentUser = new UserEnvironment(userId); 86 87 synchronized (sLock) { 88 sPrimaryVolume = null; 89 } 90 } 91 92 /** {@hide} */ 93 public static class UserEnvironment { 94 // TODO: generalize further to create package-specific environment 95 96 private final File mExternalStorage; 97 private final File mExternalStorageAndroidData; 98 private final File mExternalStorageAndroidMedia; 99 private final File mExternalStorageAndroidObb; 100 private final File mMediaStorage; 101 102 public UserEnvironment(int userId) { 103 // See storage config details at http://source.android.com/tech/storage/ 104 String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE); 105 String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); 106 String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE); 107 if (TextUtils.isEmpty(rawMediaStorage)) { 108 rawMediaStorage = "/data/media"; 109 } 110 111 if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) { 112 // Device has emulated storage; external storage paths should have 113 // userId burned into them. 114 final String rawUserId = Integer.toString(userId); 115 final File emulatedBase = new File(rawEmulatedStorageTarget); 116 final File mediaBase = new File(rawMediaStorage); 117 118 // /storage/emulated/0 119 mExternalStorage = buildPath(emulatedBase, rawUserId); 120 // /data/media/0 121 mMediaStorage = buildPath(mediaBase, rawUserId); 122 123 } else { 124 // Device has physical external storage; use plain paths. 125 if (TextUtils.isEmpty(rawExternalStorage)) { 126 Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default"); 127 rawExternalStorage = "/storage/sdcard0"; 128 } 129 130 // /storage/sdcard0 131 mExternalStorage = new File(rawExternalStorage); 132 // /data/media 133 mMediaStorage = new File(rawMediaStorage); 134 } 135 136 mExternalStorageAndroidObb = buildPath(mExternalStorage, DIRECTORY_ANDROID, "obb"); 137 mExternalStorageAndroidData = buildPath(mExternalStorage, DIRECTORY_ANDROID, "data"); 138 mExternalStorageAndroidMedia = buildPath(mExternalStorage, DIRECTORY_ANDROID, "media"); 139 } 140 141 public File getExternalStorageDirectory() { 142 return mExternalStorage; 143 } 144 145 public File getExternalStorageObbDirectory() { 146 return mExternalStorageAndroidObb; 147 } 148 149 public File getExternalStoragePublicDirectory(String type) { 150 return new File(mExternalStorage, type); 151 } 152 153 public File getExternalStorageAndroidDataDir() { 154 return mExternalStorageAndroidData; 155 } 156 157 public File getExternalStorageAppDataDirectory(String packageName) { 158 return new File(mExternalStorageAndroidData, packageName); 159 } 160 161 public File getExternalStorageAppMediaDirectory(String packageName) { 162 return new File(mExternalStorageAndroidMedia, packageName); 163 } 164 165 public File getExternalStorageAppObbDirectory(String packageName) { 166 return new File(mExternalStorageAndroidObb, packageName); 167 } 168 169 public File getExternalStorageAppFilesDirectory(String packageName) { 170 return new File(new File(mExternalStorageAndroidData, packageName), "files"); 171 } 172 173 public File getExternalStorageAppCacheDirectory(String packageName) { 174 return new File(new File(mExternalStorageAndroidData, packageName), "cache"); 175 } 176 177 public File getMediaStorageDirectory() { 178 return mMediaStorage; 179 } 180 } 181 182 /** 183 * Gets the Android root directory. 184 */ 185 public static File getRootDirectory() { 186 return DIR_ANDROID_ROOT; 187 } 188 189 /** 190 * Gets the system directory available for secure storage. 191 * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system). 192 * Otherwise, it returns the unencrypted /data/system directory. 193 * @return File object representing the secure storage system directory. 194 * @hide 195 */ 196 public static File getSystemSecureDirectory() { 197 if (isEncryptedFilesystemEnabled()) { 198 return new File(SECURE_DATA_DIRECTORY, "system"); 199 } else { 200 return new File(DATA_DIRECTORY, "system"); 201 } 202 } 203 204 /** 205 * Gets the data directory for secure storage. 206 * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure). 207 * Otherwise, it returns the unencrypted /data directory. 208 * @return File object representing the data directory for secure storage. 209 * @hide 210 */ 211 public static File getSecureDataDirectory() { 212 if (isEncryptedFilesystemEnabled()) { 213 return SECURE_DATA_DIRECTORY; 214 } else { 215 return DATA_DIRECTORY; 216 } 217 } 218 219 /** 220 * Return directory used for internal media storage, which is protected by 221 * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. 222 * 223 * @hide 224 */ 225 public static File getMediaStorageDirectory() { 226 throwIfSystem(); 227 return sCurrentUser.getMediaStorageDirectory(); 228 } 229 230 /** 231 * Return the system directory for a user. This is for use by system services to store 232 * files relating to the user. This directory will be automatically deleted when the user 233 * is removed. 234 * 235 * @hide 236 */ 237 public static File getUserSystemDirectory(int userId) { 238 return new File(new File(getSystemSecureDirectory(), "users"), Integer.toString(userId)); 239 } 240 241 /** 242 * Returns whether the Encrypted File System feature is enabled on the device or not. 243 * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code> 244 * if disabled. 245 * @hide 246 */ 247 public static boolean isEncryptedFilesystemEnabled() { 248 return SystemProperties.getBoolean(SYSTEM_PROPERTY_EFS_ENABLED, false); 249 } 250 251 private static final File DATA_DIRECTORY 252 = getDirectory("ANDROID_DATA", "/data"); 253 254 /** 255 * @hide 256 */ 257 private static final File SECURE_DATA_DIRECTORY 258 = getDirectory("ANDROID_SECURE_DATA", "/data/secure"); 259 260 private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache"); 261 262 /** 263 * Gets the Android data directory. 264 */ 265 public static File getDataDirectory() { 266 return DATA_DIRECTORY; 267 } 268 269 /** 270 * Gets the Android external storage directory. This directory may not 271 * currently be accessible if it has been mounted by the user on their 272 * computer, has been removed from the device, or some other problem has 273 * happened. You can determine its current state with 274 * {@link #getExternalStorageState()}. 275 * 276 * <p><em>Note: don't be confused by the word "external" here. This 277 * directory can better be thought as media/shared storage. It is a 278 * filesystem that can hold a relatively large amount of data and that 279 * is shared across all applications (does not enforce permissions). 280 * Traditionally this is an SD card, but it may also be implemented as 281 * built-in storage in a device that is distinct from the protected 282 * internal storage and can be mounted as a filesystem on a computer.</em></p> 283 * 284 * <p>On devices with multiple users (as described by {@link UserManager}), 285 * each user has their own isolated external storage. Applications only 286 * have access to the external storage for the user they're running as.</p> 287 * 288 * <p>In devices with multiple "external" storage directories (such as 289 * both secure app storage and mountable shared storage), this directory 290 * represents the "primary" external storage that the user will interact 291 * with.</p> 292 * 293 * <p>Applications should not directly use this top-level directory, in 294 * order to avoid polluting the user's root namespace. Any files that are 295 * private to the application should be placed in a directory returned 296 * by {@link android.content.Context#getExternalFilesDir 297 * Context.getExternalFilesDir}, which the system will take care of deleting 298 * if the application is uninstalled. Other shared files should be placed 299 * in one of the directories returned by 300 * {@link #getExternalStoragePublicDirectory}.</p> 301 * 302 * <p>Writing to this path requires the 303 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission. In 304 * a future platform release, access to this path will require the 305 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission, 306 * which is automatically granted if you hold the write permission.</p> 307 * 308 * <p>This path may change between platform versions, so applications 309 * should only persist relative paths.</p> 310 * 311 * <p>Here is an example of typical code to monitor the state of 312 * external storage:</p> 313 * 314 * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java 315 * monitor_storage} 316 * 317 * @see #getExternalStorageState() 318 * @see #isExternalStorageRemovable() 319 */ 320 public static File getExternalStorageDirectory() { 321 throwIfSystem(); 322 return sCurrentUser.getExternalStorageDirectory(); 323 } 324 325 /** {@hide} */ 326 public static File getLegacyExternalStorageDirectory() { 327 return new File(System.getenv(ENV_EXTERNAL_STORAGE)); 328 } 329 330 /** {@hide} */ 331 public static File getLegacyExternalStorageObbDirectory() { 332 return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb"); 333 } 334 335 /** {@hide} */ 336 public static File getEmulatedStorageSource(int userId) { 337 // /mnt/shell/emulated/0 338 return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId)); 339 } 340 341 /** {@hide} */ 342 public static File getEmulatedStorageObbSource() { 343 // /mnt/shell/emulated/obb 344 return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), "obb"); 345 } 346 347 /** 348 * Standard directory in which to place any audio files that should be 349 * in the regular list of music for the user. 350 * This may be combined with 351 * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, 352 * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series 353 * of directories to categories a particular audio file as more than one 354 * type. 355 */ 356 public static String DIRECTORY_MUSIC = "Music"; 357 358 /** 359 * Standard directory in which to place any audio files that should be 360 * in the list of podcasts that the user can select (not as regular 361 * music). 362 * This may be combined with {@link #DIRECTORY_MUSIC}, 363 * {@link #DIRECTORY_NOTIFICATIONS}, 364 * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series 365 * of directories to categories a particular audio file as more than one 366 * type. 367 */ 368 public static String DIRECTORY_PODCASTS = "Podcasts"; 369 370 /** 371 * Standard directory in which to place any audio files that should be 372 * in the list of ringtones that the user can select (not as regular 373 * music). 374 * This may be combined with {@link #DIRECTORY_MUSIC}, 375 * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, and 376 * {@link #DIRECTORY_ALARMS} as a series 377 * of directories to categories a particular audio file as more than one 378 * type. 379 */ 380 public static String DIRECTORY_RINGTONES = "Ringtones"; 381 382 /** 383 * Standard directory in which to place any audio files that should be 384 * in the list of alarms that the user can select (not as regular 385 * music). 386 * This may be combined with {@link #DIRECTORY_MUSIC}, 387 * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, 388 * and {@link #DIRECTORY_RINGTONES} as a series 389 * of directories to categories a particular audio file as more than one 390 * type. 391 */ 392 public static String DIRECTORY_ALARMS = "Alarms"; 393 394 /** 395 * Standard directory in which to place any audio files that should be 396 * in the list of notifications that the user can select (not as regular 397 * music). 398 * This may be combined with {@link #DIRECTORY_MUSIC}, 399 * {@link #DIRECTORY_PODCASTS}, 400 * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series 401 * of directories to categories a particular audio file as more than one 402 * type. 403 */ 404 public static String DIRECTORY_NOTIFICATIONS = "Notifications"; 405 406 /** 407 * Standard directory in which to place pictures that are available to 408 * the user. Note that this is primarily a convention for the top-level 409 * public directory, as the media scanner will find and collect pictures 410 * in any directory. 411 */ 412 public static String DIRECTORY_PICTURES = "Pictures"; 413 414 /** 415 * Standard directory in which to place movies that are available to 416 * the user. Note that this is primarily a convention for the top-level 417 * public directory, as the media scanner will find and collect movies 418 * in any directory. 419 */ 420 public static String DIRECTORY_MOVIES = "Movies"; 421 422 /** 423 * Standard directory in which to place files that have been downloaded by 424 * the user. Note that this is primarily a convention for the top-level 425 * public directory, you are free to download files anywhere in your own 426 * private directories. Also note that though the constant here is 427 * named DIRECTORY_DOWNLOADS (plural), the actual file name is non-plural for 428 * backwards compatibility reasons. 429 */ 430 public static String DIRECTORY_DOWNLOADS = "Download"; 431 432 /** 433 * The traditional location for pictures and videos when mounting the 434 * device as a camera. Note that this is primarily a convention for the 435 * top-level public directory, as this convention makes no sense elsewhere. 436 */ 437 public static String DIRECTORY_DCIM = "DCIM"; 438 439 /** 440 * Get a top-level public external storage directory for placing files of 441 * a particular type. This is where the user will typically place and 442 * manage their own files, so you should be careful about what you put here 443 * to ensure you don't erase their files or get in the way of their own 444 * organization. 445 * 446 * <p>On devices with multiple users (as described by {@link UserManager}), 447 * each user has their own isolated external storage. Applications only 448 * have access to the external storage for the user they're running as.</p> 449 * 450 * <p>Here is an example of typical code to manipulate a picture on 451 * the public external storage:</p> 452 * 453 * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java 454 * public_picture} 455 * 456 * @param type The type of storage directory to return. Should be one of 457 * {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS}, 458 * {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS}, 459 * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES}, 460 * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or 461 * {@link #DIRECTORY_DCIM}. May not be null. 462 * 463 * @return Returns the File path for the directory. Note that this 464 * directory may not yet exist, so you must make sure it exists before 465 * using it such as with {@link File#mkdirs File.mkdirs()}. 466 */ 467 public static File getExternalStoragePublicDirectory(String type) { 468 throwIfSystem(); 469 return sCurrentUser.getExternalStoragePublicDirectory(type); 470 } 471 472 /** 473 * Returns the path for android-specific data on the SD card. 474 * @hide 475 */ 476 public static File getExternalStorageAndroidDataDir() { 477 throwIfSystem(); 478 return sCurrentUser.getExternalStorageAndroidDataDir(); 479 } 480 481 /** 482 * Generates the raw path to an application's data 483 * @hide 484 */ 485 public static File getExternalStorageAppDataDirectory(String packageName) { 486 throwIfSystem(); 487 return sCurrentUser.getExternalStorageAppDataDirectory(packageName); 488 } 489 490 /** 491 * Generates the raw path to an application's media 492 * @hide 493 */ 494 public static File getExternalStorageAppMediaDirectory(String packageName) { 495 throwIfSystem(); 496 return sCurrentUser.getExternalStorageAppMediaDirectory(packageName); 497 } 498 499 /** 500 * Generates the raw path to an application's OBB files 501 * @hide 502 */ 503 public static File getExternalStorageAppObbDirectory(String packageName) { 504 throwIfSystem(); 505 return sCurrentUser.getExternalStorageAppObbDirectory(packageName); 506 } 507 508 /** 509 * Generates the path to an application's files. 510 * @hide 511 */ 512 public static File getExternalStorageAppFilesDirectory(String packageName) { 513 throwIfSystem(); 514 return sCurrentUser.getExternalStorageAppFilesDirectory(packageName); 515 } 516 517 /** 518 * Generates the path to an application's cache. 519 * @hide 520 */ 521 public static File getExternalStorageAppCacheDirectory(String packageName) { 522 throwIfSystem(); 523 return sCurrentUser.getExternalStorageAppCacheDirectory(packageName); 524 } 525 526 /** 527 * Gets the Android download/cache content directory. 528 */ 529 public static File getDownloadCacheDirectory() { 530 return DOWNLOAD_CACHE_DIRECTORY; 531 } 532 533 /** 534 * {@link #getExternalStorageState()} returns MEDIA_REMOVED if the media is not present. 535 */ 536 public static final String MEDIA_REMOVED = "removed"; 537 538 /** 539 * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTED if the media is present 540 * but not mounted. 541 */ 542 public static final String MEDIA_UNMOUNTED = "unmounted"; 543 544 /** 545 * {@link #getExternalStorageState()} returns MEDIA_CHECKING if the media is present 546 * and being disk-checked 547 */ 548 public static final String MEDIA_CHECKING = "checking"; 549 550 /** 551 * {@link #getExternalStorageState()} returns MEDIA_NOFS if the media is present 552 * but is blank or is using an unsupported filesystem 553 */ 554 public static final String MEDIA_NOFS = "nofs"; 555 556 /** 557 * {@link #getExternalStorageState()} returns MEDIA_MOUNTED if the media is present 558 * and mounted at its mount point with read/write access. 559 */ 560 public static final String MEDIA_MOUNTED = "mounted"; 561 562 /** 563 * {@link #getExternalStorageState()} returns MEDIA_MOUNTED_READ_ONLY if the media is present 564 * and mounted at its mount point with read only access. 565 */ 566 public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro"; 567 568 /** 569 * {@link #getExternalStorageState()} returns MEDIA_SHARED if the media is present 570 * not mounted, and shared via USB mass storage. 571 */ 572 public static final String MEDIA_SHARED = "shared"; 573 574 /** 575 * {@link #getExternalStorageState()} returns MEDIA_BAD_REMOVAL if the media was 576 * removed before it was unmounted. 577 */ 578 public static final String MEDIA_BAD_REMOVAL = "bad_removal"; 579 580 /** 581 * {@link #getExternalStorageState()} returns MEDIA_UNMOUNTABLE if the media is present 582 * but cannot be mounted. Typically this happens if the file system on the 583 * media is corrupted. 584 */ 585 public static final String MEDIA_UNMOUNTABLE = "unmountable"; 586 587 /** 588 * Gets the current state of the primary "external" storage device. 589 * 590 * @see #getExternalStorageDirectory() 591 */ 592 public static String getExternalStorageState() { 593 try { 594 IMountService mountService = IMountService.Stub.asInterface(ServiceManager 595 .getService("mount")); 596 final StorageVolume primary = getPrimaryVolume(); 597 return mountService.getVolumeState(primary.getPath()); 598 } catch (RemoteException rex) { 599 Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex); 600 return Environment.MEDIA_REMOVED; 601 } 602 } 603 604 /** 605 * Returns whether the primary "external" storage device is removable. 606 * If true is returned, this device is for example an SD card that the 607 * user can remove. If false is returned, the storage is built into 608 * the device and can not be physically removed. 609 * 610 * <p>See {@link #getExternalStorageDirectory()} for more information. 611 */ 612 public static boolean isExternalStorageRemovable() { 613 final StorageVolume primary = getPrimaryVolume(); 614 return (primary != null && primary.isRemovable()); 615 } 616 617 /** 618 * Returns whether the device has an external storage device which is 619 * emulated. If true, the device does not have real external storage, and the directory 620 * returned by {@link #getExternalStorageDirectory()} will be allocated using a portion of 621 * the internal storage system. 622 * 623 * <p>Certain system services, such as the package manager, use this 624 * to determine where to install an application. 625 * 626 * <p>Emulated external storage may also be encrypted - see 627 * {@link android.app.admin.DevicePolicyManager#setStorageEncryption( 628 * android.content.ComponentName, boolean)} for additional details. 629 */ 630 public static boolean isExternalStorageEmulated() { 631 final StorageVolume primary = getPrimaryVolume(); 632 return (primary != null && primary.isEmulated()); 633 } 634 635 static File getDirectory(String variableName, String defaultPath) { 636 String path = System.getenv(variableName); 637 return path == null ? new File(defaultPath) : new File(path); 638 } 639 640 private static String getCanonicalPathOrNull(String variableName) { 641 String path = System.getenv(variableName); 642 if (path == null) { 643 return null; 644 } 645 try { 646 return new File(path).getCanonicalPath(); 647 } catch (IOException e) { 648 Log.w(TAG, "Unable to resolve canonical path for " + path); 649 return null; 650 } 651 } 652 653 private static void throwIfSystem() { 654 if (Process.myUid() == Process.SYSTEM_UID) { 655 Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable()); 656 } 657 } 658 659 private static File buildPath(File base, String... segments) { 660 File cur = base; 661 for (String segment : segments) { 662 if (cur == null) { 663 cur = new File(segment); 664 } else { 665 cur = new File(cur, segment); 666 } 667 } 668 return cur; 669 } 670 671 /** 672 * If the given path exists on emulated external storage, return the 673 * translated backing path hosted on internal storage. This bypasses any 674 * emulation later, improving performance. This is <em>only</em> suitable 675 * for read-only access. 676 * <p> 677 * Returns original path if given path doesn't meet these criteria. Callers 678 * must hold {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} 679 * permission. 680 * 681 * @hide 682 */ 683 public static File maybeTranslateEmulatedPathToInternal(File path) { 684 // Fast return if not emulated, or missing variables 685 if (!Environment.isExternalStorageEmulated() 686 || CANONCIAL_EMULATED_STORAGE_TARGET == null) { 687 return path; 688 } 689 690 try { 691 final String rawPath = path.getCanonicalPath(); 692 if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) { 693 final File internalPath = new File(DIR_MEDIA_STORAGE, 694 rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length())); 695 if (internalPath.exists()) { 696 return internalPath; 697 } 698 } 699 } catch (IOException e) { 700 Log.w(TAG, "Failed to resolve canonical path for " + path); 701 } 702 703 // Unable to translate to internal path; use original 704 return path; 705 } 706} 707