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