1 /* 2 * Copyright (C) 2015 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 android.os.storage; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.res.Resources; 25 import android.net.Uri; 26 import android.os.Build; 27 import android.os.Environment; 28 import android.os.IVold; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.os.UserHandle; 32 import android.provider.DocumentsContract; 33 import android.text.TextUtils; 34 import android.util.ArrayMap; 35 import android.util.DebugUtils; 36 import android.util.SparseArray; 37 import android.util.SparseIntArray; 38 39 import com.android.internal.R; 40 import com.android.internal.util.IndentingPrintWriter; 41 import com.android.internal.util.Preconditions; 42 43 import java.io.CharArrayWriter; 44 import java.io.File; 45 import java.util.Comparator; 46 import java.util.Locale; 47 import java.util.Objects; 48 import java.util.UUID; 49 50 /** 51 * Information about a storage volume that may be mounted. A volume may be a 52 * partition on a physical {@link DiskInfo}, an emulated volume above some other 53 * storage medium, or a standalone container like an ASEC or OBB. 54 * <p> 55 * Volumes may be mounted with various flags: 56 * <ul> 57 * <li>{@link #MOUNT_FLAG_PRIMARY} means the volume provides primary external 58 * storage, historically found at {@code /sdcard}. 59 * <li>{@link #MOUNT_FLAG_VISIBLE_FOR_READ} and 60 * {@link #MOUNT_FLAG_VISIBLE_FOR_WRITE} mean the volume is visible to 61 * third-party apps for direct filesystem access. The system should send out 62 * relevant storage broadcasts and index any media on visible volumes. Visible 63 * volumes are considered a more stable part of the device, which is why we take 64 * the time to index them. In particular, transient volumes like USB OTG devices 65 * <em>should not</em> be marked as visible; their contents should be surfaced 66 * to apps through the Storage Access Framework. 67 * </ul> 68 * 69 * @hide 70 */ 71 public class VolumeInfo implements Parcelable { 72 public static final String ACTION_VOLUME_STATE_CHANGED = 73 "android.os.storage.action.VOLUME_STATE_CHANGED"; 74 public static final String EXTRA_VOLUME_ID = 75 "android.os.storage.extra.VOLUME_ID"; 76 public static final String EXTRA_VOLUME_STATE = 77 "android.os.storage.extra.VOLUME_STATE"; 78 79 /** Stub volume representing internal private storage */ 80 public static final String ID_PRIVATE_INTERNAL = "private"; 81 /** Real volume representing internal emulated storage */ 82 public static final String ID_EMULATED_INTERNAL = "emulated"; 83 84 @UnsupportedAppUsage 85 public static final int TYPE_PUBLIC = IVold.VOLUME_TYPE_PUBLIC; 86 public static final int TYPE_PRIVATE = IVold.VOLUME_TYPE_PRIVATE; 87 @UnsupportedAppUsage 88 public static final int TYPE_EMULATED = IVold.VOLUME_TYPE_EMULATED; 89 public static final int TYPE_ASEC = IVold.VOLUME_TYPE_ASEC; 90 public static final int TYPE_OBB = IVold.VOLUME_TYPE_OBB; 91 public static final int TYPE_STUB = IVold.VOLUME_TYPE_STUB; 92 93 public static final int STATE_UNMOUNTED = IVold.VOLUME_STATE_UNMOUNTED; 94 public static final int STATE_CHECKING = IVold.VOLUME_STATE_CHECKING; 95 public static final int STATE_MOUNTED = IVold.VOLUME_STATE_MOUNTED; 96 public static final int STATE_MOUNTED_READ_ONLY = IVold.VOLUME_STATE_MOUNTED_READ_ONLY; 97 public static final int STATE_FORMATTING = IVold.VOLUME_STATE_FORMATTING; 98 public static final int STATE_EJECTING = IVold.VOLUME_STATE_EJECTING; 99 public static final int STATE_UNMOUNTABLE = IVold.VOLUME_STATE_UNMOUNTABLE; 100 public static final int STATE_REMOVED = IVold.VOLUME_STATE_REMOVED; 101 public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL; 102 103 public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY; 104 public static final int MOUNT_FLAG_VISIBLE_FOR_READ = IVold.MOUNT_FLAG_VISIBLE_FOR_READ; 105 public static final int MOUNT_FLAG_VISIBLE_FOR_WRITE = IVold.MOUNT_FLAG_VISIBLE_FOR_WRITE; 106 107 private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); 108 private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); 109 private static SparseIntArray sStateToDescrip = new SparseIntArray(); 110 111 private static final Comparator<VolumeInfo> 112 sDescriptionComparator = new Comparator<VolumeInfo>() { 113 @Override 114 public int compare(VolumeInfo lhs, VolumeInfo rhs) { 115 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) { 116 return -1; 117 } else if (lhs.getDescription() == null) { 118 return 1; 119 } else if (rhs.getDescription() == null) { 120 return -1; 121 } else { 122 return lhs.getDescription().compareTo(rhs.getDescription()); 123 } 124 } 125 }; 126 127 static { sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED)128 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED); sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING)129 sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING); sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED)130 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED); sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY)131 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY); sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED)132 sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED); sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING)133 sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING); sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE)134 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE); sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED)135 sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED); sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL)136 sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL); 137 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED)138 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED); sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING)139 sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING); sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED)140 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED); sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED)141 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED); sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT)142 sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT); sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE)143 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE); sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED)144 sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED); sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL)145 sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL); 146 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted)147 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted); sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking)148 sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking); sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted)149 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted); sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro)150 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro); sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting)151 sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting); sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting)152 sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting); sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable)153 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable); sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed)154 sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed); sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal)155 sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal); 156 } 157 158 /** vold state */ 159 public final String id; 160 @UnsupportedAppUsage 161 public final int type; 162 @UnsupportedAppUsage 163 public final DiskInfo disk; 164 public final String partGuid; 165 public int mountFlags = 0; 166 public int mountUserId = UserHandle.USER_NULL; 167 @UnsupportedAppUsage 168 public int state = STATE_UNMOUNTED; 169 public String fsType; 170 @UnsupportedAppUsage 171 public String fsUuid; 172 @UnsupportedAppUsage 173 public String fsLabel; 174 @UnsupportedAppUsage 175 public String path; 176 @UnsupportedAppUsage 177 public String internalPath; 178 VolumeInfo(String id, int type, DiskInfo disk, String partGuid)179 public VolumeInfo(String id, int type, DiskInfo disk, String partGuid) { 180 this.id = Preconditions.checkNotNull(id); 181 this.type = type; 182 this.disk = disk; 183 this.partGuid = partGuid; 184 } 185 186 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) VolumeInfo(Parcel parcel)187 public VolumeInfo(Parcel parcel) { 188 id = parcel.readString8(); 189 type = parcel.readInt(); 190 if (parcel.readInt() != 0) { 191 disk = DiskInfo.CREATOR.createFromParcel(parcel); 192 } else { 193 disk = null; 194 } 195 partGuid = parcel.readString8(); 196 mountFlags = parcel.readInt(); 197 mountUserId = parcel.readInt(); 198 state = parcel.readInt(); 199 fsType = parcel.readString8(); 200 fsUuid = parcel.readString8(); 201 fsLabel = parcel.readString8(); 202 path = parcel.readString8(); 203 internalPath = parcel.readString8(); 204 } 205 VolumeInfo(VolumeInfo volumeInfo)206 public VolumeInfo(VolumeInfo volumeInfo) { 207 this.id = volumeInfo.id; 208 this.type = volumeInfo.type; 209 this.disk = volumeInfo.disk; 210 this.partGuid = volumeInfo.partGuid; 211 this.mountFlags = volumeInfo.mountFlags; 212 this.mountUserId = volumeInfo.mountUserId; 213 this.state = volumeInfo.state; 214 this.fsType = volumeInfo.fsType; 215 this.fsUuid = volumeInfo.fsUuid; 216 this.fsLabel = volumeInfo.fsLabel; 217 this.path = volumeInfo.path; 218 this.internalPath = volumeInfo.internalPath; 219 } 220 221 @UnsupportedAppUsage getEnvironmentForState(int state)222 public static @NonNull String getEnvironmentForState(int state) { 223 final String envState = sStateToEnvironment.get(state); 224 if (envState != null) { 225 return envState; 226 } else { 227 return Environment.MEDIA_UNKNOWN; 228 } 229 } 230 getBroadcastForEnvironment(String envState)231 public static @Nullable String getBroadcastForEnvironment(String envState) { 232 return sEnvironmentToBroadcast.get(envState); 233 } 234 getBroadcastForState(int state)235 public static @Nullable String getBroadcastForState(int state) { 236 return getBroadcastForEnvironment(getEnvironmentForState(state)); 237 } 238 getDescriptionComparator()239 public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() { 240 return sDescriptionComparator; 241 } 242 243 @UnsupportedAppUsage getId()244 public @NonNull String getId() { 245 return id; 246 } 247 248 @UnsupportedAppUsage getDisk()249 public @Nullable DiskInfo getDisk() { 250 return disk; 251 } 252 253 @UnsupportedAppUsage getDiskId()254 public @Nullable String getDiskId() { 255 return (disk != null) ? disk.id : null; 256 } 257 258 @UnsupportedAppUsage getType()259 public int getType() { 260 return type; 261 } 262 263 @UnsupportedAppUsage getState()264 public int getState() { 265 return state; 266 } 267 getStateDescription()268 public int getStateDescription() { 269 return sStateToDescrip.get(state, 0); 270 } 271 272 @UnsupportedAppUsage getFsUuid()273 public @Nullable String getFsUuid() { 274 return fsUuid; 275 } 276 getNormalizedFsUuid()277 public @Nullable String getNormalizedFsUuid() { 278 return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null; 279 } 280 281 @UnsupportedAppUsage getMountUserId()282 public int getMountUserId() { 283 return mountUserId; 284 } 285 286 @UnsupportedAppUsage getDescription()287 public @Nullable String getDescription() { 288 if (ID_PRIVATE_INTERNAL.equals(id) || id.startsWith(ID_EMULATED_INTERNAL + ";")) { 289 return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); 290 } else if (!TextUtils.isEmpty(fsLabel)) { 291 return fsLabel; 292 } else { 293 return null; 294 } 295 } 296 297 @UnsupportedAppUsage isMountedReadable()298 public boolean isMountedReadable() { 299 return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY; 300 } 301 302 @UnsupportedAppUsage isMountedWritable()303 public boolean isMountedWritable() { 304 return state == STATE_MOUNTED; 305 } 306 307 @UnsupportedAppUsage isPrimary()308 public boolean isPrimary() { 309 return (mountFlags & MOUNT_FLAG_PRIMARY) != 0; 310 } 311 312 @UnsupportedAppUsage isPrimaryPhysical()313 public boolean isPrimaryPhysical() { 314 return isPrimary() && (getType() == TYPE_PUBLIC); 315 } 316 isVisibleForRead()317 private boolean isVisibleForRead() { 318 return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_READ) != 0; 319 } 320 isVisibleForWrite()321 private boolean isVisibleForWrite() { 322 return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_WRITE) != 0; 323 } 324 325 @UnsupportedAppUsage isVisible()326 public boolean isVisible() { 327 return isVisibleForRead() || isVisibleForWrite(); 328 } 329 isVolumeSupportedForUser(int userId)330 private boolean isVolumeSupportedForUser(int userId) { 331 if (mountUserId != userId) { 332 return false; 333 } 334 return type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED; 335 } 336 337 /** 338 * Returns {@code true} if this volume is visible for {@code userId}, {@code false} otherwise. 339 */ isVisibleForUser(int userId)340 public boolean isVisibleForUser(int userId) { 341 return isVolumeSupportedForUser(userId) && isVisible(); 342 } 343 344 /** 345 * Returns {@code true} if this volume is the primary emulated volume for {@code userId}, 346 * {@code false} otherwise. 347 */ 348 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isPrimaryEmulatedForUser(int userId)349 public boolean isPrimaryEmulatedForUser(int userId) { 350 return id.equals(ID_EMULATED_INTERNAL + ";" + userId); 351 } 352 isVisibleForRead(int userId)353 public boolean isVisibleForRead(int userId) { 354 return isVolumeSupportedForUser(userId) && isVisibleForRead(); 355 } 356 357 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isVisibleForWrite(int userId)358 public boolean isVisibleForWrite(int userId) { 359 return isVolumeSupportedForUser(userId) && isVisibleForWrite(); 360 } 361 362 @UnsupportedAppUsage getPath()363 public File getPath() { 364 return (path != null) ? new File(path) : null; 365 } 366 367 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getInternalPath()368 public File getInternalPath() { 369 return (internalPath != null) ? new File(internalPath) : null; 370 } 371 372 @UnsupportedAppUsage getPathForUser(int userId)373 public File getPathForUser(int userId) { 374 if (path == null) { 375 return null; 376 } else if (type == TYPE_PUBLIC || type == TYPE_STUB) { 377 return new File(path); 378 } else if (type == TYPE_EMULATED) { 379 return new File(path, Integer.toString(userId)); 380 } else { 381 return null; 382 } 383 } 384 385 /** 386 * Path which is accessible to apps holding 387 * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. 388 */ 389 @UnsupportedAppUsage getInternalPathForUser(int userId)390 public File getInternalPathForUser(int userId) { 391 if (path == null) { 392 return null; 393 } else if (type == TYPE_PUBLIC || type == TYPE_STUB) { 394 // TODO: plumb through cleaner path from vold 395 return new File(path.replace("/storage/", "/mnt/media_rw/")); 396 } else { 397 return getPathForUser(userId); 398 } 399 } 400 401 @UnsupportedAppUsage buildStorageVolume(Context context, int userId, boolean reportUnmounted)402 public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) { 403 final StorageManager storage = context.getSystemService(StorageManager.class); 404 405 final boolean removable; 406 final boolean emulated; 407 final boolean externallyManaged = type == TYPE_STUB; 408 final boolean allowMassStorage = false; 409 final String envState = reportUnmounted 410 ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state); 411 412 File userPath = getPathForUser(userId); 413 if (userPath == null) { 414 userPath = new File("/dev/null"); 415 } 416 File internalPath = getInternalPathForUser(userId); 417 if (internalPath == null) { 418 internalPath = new File("/dev/null"); 419 } 420 421 String description = null; 422 UUID uuid = null; 423 String derivedFsUuid = fsUuid; 424 long maxFileSize = 0; 425 426 if (type == TYPE_EMULATED) { 427 emulated = true; 428 429 final VolumeInfo privateVol = storage.findPrivateForEmulated(this); 430 if (privateVol != null) { 431 description = storage.getBestVolumeDescription(privateVol); 432 uuid = StorageManager.convert(privateVol.fsUuid); 433 derivedFsUuid = privateVol.fsUuid; 434 } else { 435 uuid = StorageManager.UUID_DEFAULT; 436 } 437 438 if (isPrimaryEmulatedForUser(userId)) { 439 removable = false; 440 } else { 441 removable = true; 442 } 443 444 } else if (type == TYPE_PUBLIC || type == TYPE_STUB) { 445 emulated = false; 446 removable = true; 447 448 description = storage.getBestVolumeDescription(this); 449 450 if ("vfat".equals(fsType)) { 451 maxFileSize = 4294967295L; 452 } 453 454 } else { 455 throw new IllegalStateException("Unexpected volume type " + type); 456 } 457 458 if (description == null) { 459 description = context.getString(android.R.string.unknownName); 460 } 461 462 return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable, 463 emulated, externallyManaged, allowMassStorage, maxFileSize, new UserHandle(userId), 464 uuid, derivedFsUuid, envState); 465 } 466 467 @UnsupportedAppUsage buildStableMtpStorageId(String fsUuid)468 public static int buildStableMtpStorageId(String fsUuid) { 469 if (TextUtils.isEmpty(fsUuid)) { 470 return StorageVolume.STORAGE_ID_INVALID; 471 } else { 472 int hash = 0; 473 for (int i = 0; i < fsUuid.length(); ++i) { 474 hash = 31 * hash + fsUuid.charAt(i); 475 } 476 hash = (hash ^ (hash << 16)) & 0xffff0000; 477 // Work around values that the spec doesn't allow, or that we've 478 // reserved for primary 479 if (hash == 0x00000000) hash = 0x00020000; 480 if (hash == 0x00010000) hash = 0x00020000; 481 if (hash == 0xffff0000) hash = 0xfffe0000; 482 return hash | 0x0001; 483 } 484 } 485 486 // TODO: avoid this layering violation 487 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; 488 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; 489 490 /** 491 * Build an intent to browse the contents of this volume. Only valid for 492 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}. 493 */ 494 @UnsupportedAppUsage buildBrowseIntent()495 public @Nullable Intent buildBrowseIntent() { 496 return buildBrowseIntentForUser(UserHandle.myUserId()); 497 } 498 buildBrowseIntentForUser(int userId)499 public @Nullable Intent buildBrowseIntentForUser(int userId) { 500 final Uri uri; 501 if ((type == VolumeInfo.TYPE_PUBLIC || type == VolumeInfo.TYPE_STUB) 502 && mountUserId == userId) { 503 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); 504 } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) { 505 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, 506 DOCUMENT_ROOT_PRIMARY_EMULATED); 507 } else { 508 return null; 509 } 510 511 final Intent intent = new Intent(Intent.ACTION_VIEW); 512 intent.addCategory(Intent.CATEGORY_DEFAULT); 513 intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM); 514 515 // note that docsui treats this as *force* show advanced. So sending 516 // false permits advanced to be shown based on user preferences. 517 intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary()); 518 return intent; 519 } 520 521 @Override toString()522 public String toString() { 523 final CharArrayWriter writer = new CharArrayWriter(); 524 dump(new IndentingPrintWriter(writer, " ", 80)); 525 return writer.toString(); 526 } 527 dump(IndentingPrintWriter pw)528 public void dump(IndentingPrintWriter pw) { 529 pw.println("VolumeInfo{" + id + "}:"); 530 pw.increaseIndent(); 531 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type)); 532 pw.printPair("diskId", getDiskId()); 533 pw.printPair("partGuid", partGuid); 534 pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags)); 535 pw.printPair("mountUserId", mountUserId); 536 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state)); 537 pw.println(); 538 pw.printPair("fsType", fsType); 539 pw.printPair("fsUuid", fsUuid); 540 pw.printPair("fsLabel", fsLabel); 541 pw.println(); 542 pw.printPair("path", path); 543 pw.printPair("internalPath", internalPath); 544 pw.decreaseIndent(); 545 pw.println(); 546 } 547 548 @Override clone()549 public VolumeInfo clone() { 550 final Parcel temp = Parcel.obtain(); 551 try { 552 writeToParcel(temp, 0); 553 temp.setDataPosition(0); 554 return CREATOR.createFromParcel(temp); 555 } finally { 556 temp.recycle(); 557 } 558 } 559 560 @Override equals(@ullable Object o)561 public boolean equals(@Nullable Object o) { 562 if (o instanceof VolumeInfo) { 563 return Objects.equals(id, ((VolumeInfo) o).id); 564 } else { 565 return false; 566 } 567 } 568 569 @Override hashCode()570 public int hashCode() { 571 return id.hashCode(); 572 } 573 574 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 575 public static final @android.annotation.NonNull Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() { 576 @Override 577 public VolumeInfo createFromParcel(Parcel in) { 578 return new VolumeInfo(in); 579 } 580 581 @Override 582 public VolumeInfo[] newArray(int size) { 583 return new VolumeInfo[size]; 584 } 585 }; 586 587 @Override describeContents()588 public int describeContents() { 589 return 0; 590 } 591 592 @Override writeToParcel(Parcel parcel, int flags)593 public void writeToParcel(Parcel parcel, int flags) { 594 parcel.writeString8(id); 595 parcel.writeInt(type); 596 if (disk != null) { 597 parcel.writeInt(1); 598 disk.writeToParcel(parcel, flags); 599 } else { 600 parcel.writeInt(0); 601 } 602 parcel.writeString8(partGuid); 603 parcel.writeInt(mountFlags); 604 parcel.writeInt(mountUserId); 605 parcel.writeInt(state); 606 parcel.writeString8(fsType); 607 parcel.writeString8(fsUuid); 608 parcel.writeString8(fsLabel); 609 parcel.writeString8(path); 610 parcel.writeString8(internalPath); 611 } 612 } 613