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