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.readString8();
185         type = parcel.readInt();
186         if (parcel.readInt() != 0) {
187             disk = DiskInfo.CREATOR.createFromParcel(parcel);
188         } else {
189             disk = null;
190         }
191         partGuid = parcel.readString8();
192         mountFlags = parcel.readInt();
193         mountUserId = parcel.readInt();
194         state = parcel.readInt();
195         fsType = parcel.readString8();
196         fsUuid = parcel.readString8();
197         fsLabel = parcel.readString8();
198         path = parcel.readString8();
199         internalPath = parcel.readString8();
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.startsWith(ID_EMULATED_INTERNAL + ";")) {
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 || type == TYPE_EMULATED)
305                 && mountUserId == userId) {
306             return isVisible();
307         }
308         return false;
309     }
310 
311     /**
312      * Returns {@code true} if this volume is the primary emulated volume for {@code userId},
313      * {@code false} otherwise.
314      */
315     @UnsupportedAppUsage
isPrimaryEmulatedForUser(int userId)316     public boolean isPrimaryEmulatedForUser(int userId) {
317         return id.equals(ID_EMULATED_INTERNAL + ";" + userId);
318     }
319 
isVisibleForRead(int userId)320     public boolean isVisibleForRead(int userId) {
321         return isVisibleForUser(userId);
322     }
323 
324     @UnsupportedAppUsage
isVisibleForWrite(int userId)325     public boolean isVisibleForWrite(int userId) {
326         return isVisibleForUser(userId);
327     }
328 
329     @UnsupportedAppUsage
getPath()330     public File getPath() {
331         return (path != null) ? new File(path) : null;
332     }
333 
334     @UnsupportedAppUsage
getInternalPath()335     public File getInternalPath() {
336         return (internalPath != null) ? new File(internalPath) : null;
337     }
338 
339     @UnsupportedAppUsage
getPathForUser(int userId)340     public File getPathForUser(int userId) {
341         if (path == null) {
342             return null;
343         } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
344             return new File(path);
345         } else if (type == TYPE_EMULATED) {
346             return new File(path, Integer.toString(userId));
347         } else {
348             return null;
349         }
350     }
351 
352     /**
353      * Path which is accessible to apps holding
354      * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
355      */
356     @UnsupportedAppUsage
getInternalPathForUser(int userId)357     public File getInternalPathForUser(int userId) {
358         if (path == null) {
359             return null;
360         } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
361             // TODO: plumb through cleaner path from vold
362             return new File(path.replace("/storage/", "/mnt/media_rw/"));
363         } else {
364             return getPathForUser(userId);
365         }
366     }
367 
368     @UnsupportedAppUsage
buildStorageVolume(Context context, int userId, boolean reportUnmounted)369     public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
370         final StorageManager storage = context.getSystemService(StorageManager.class);
371 
372         final boolean removable;
373         final boolean emulated;
374         final boolean allowMassStorage = false;
375         final String envState = reportUnmounted
376                 ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state);
377 
378         File userPath = getPathForUser(userId);
379         if (userPath == null) {
380             userPath = new File("/dev/null");
381         }
382         File internalPath = getInternalPathForUser(userId);
383         if (internalPath == null) {
384             internalPath = new File("/dev/null");
385         }
386 
387         String description = null;
388         String derivedFsUuid = fsUuid;
389         long maxFileSize = 0;
390 
391         if (type == TYPE_EMULATED) {
392             emulated = true;
393 
394             final VolumeInfo privateVol = storage.findPrivateForEmulated(this);
395             if (privateVol != null) {
396                 description = storage.getBestVolumeDescription(privateVol);
397                 derivedFsUuid = privateVol.fsUuid;
398             }
399 
400             if (isPrimaryEmulatedForUser(userId)) {
401                 removable = false;
402             } else {
403                 removable = true;
404             }
405 
406         } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
407             emulated = false;
408             removable = true;
409 
410             description = storage.getBestVolumeDescription(this);
411 
412             if ("vfat".equals(fsType)) {
413                 maxFileSize = 4294967295L;
414             }
415 
416         } else {
417             throw new IllegalStateException("Unexpected volume type " + type);
418         }
419 
420         if (description == null) {
421             description = context.getString(android.R.string.unknownName);
422         }
423 
424         return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable,
425                 emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
426                 derivedFsUuid, envState);
427     }
428 
429     @UnsupportedAppUsage
buildStableMtpStorageId(String fsUuid)430     public static int buildStableMtpStorageId(String fsUuid) {
431         if (TextUtils.isEmpty(fsUuid)) {
432             return StorageVolume.STORAGE_ID_INVALID;
433         } else {
434             int hash = 0;
435             for (int i = 0; i < fsUuid.length(); ++i) {
436                 hash = 31 * hash + fsUuid.charAt(i);
437             }
438             hash = (hash ^ (hash << 16)) & 0xffff0000;
439             // Work around values that the spec doesn't allow, or that we've
440             // reserved for primary
441             if (hash == 0x00000000) hash = 0x00020000;
442             if (hash == 0x00010000) hash = 0x00020000;
443             if (hash == 0xffff0000) hash = 0xfffe0000;
444             return hash | 0x0001;
445         }
446     }
447 
448     // TODO: avoid this layering violation
449     private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
450     private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
451 
452     /**
453      * Build an intent to browse the contents of this volume. Only valid for
454      * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
455      */
456     @UnsupportedAppUsage
buildBrowseIntent()457     public @Nullable Intent buildBrowseIntent() {
458         return buildBrowseIntentForUser(UserHandle.myUserId());
459     }
460 
buildBrowseIntentForUser(int userId)461     public @Nullable Intent buildBrowseIntentForUser(int userId) {
462         final Uri uri;
463         if ((type == VolumeInfo.TYPE_PUBLIC || type == VolumeInfo.TYPE_STUB)
464                 && mountUserId == userId) {
465             uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
466         } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) {
467             uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
468                     DOCUMENT_ROOT_PRIMARY_EMULATED);
469         } else {
470             return null;
471         }
472 
473         final Intent intent = new Intent(Intent.ACTION_VIEW);
474         intent.addCategory(Intent.CATEGORY_DEFAULT);
475         intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
476 
477         // note that docsui treats this as *force* show advanced. So sending
478         // false permits advanced to be shown based on user preferences.
479         intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
480         return intent;
481     }
482 
483     @Override
toString()484     public String toString() {
485         final CharArrayWriter writer = new CharArrayWriter();
486         dump(new IndentingPrintWriter(writer, "    ", 80));
487         return writer.toString();
488     }
489 
dump(IndentingPrintWriter pw)490     public void dump(IndentingPrintWriter pw) {
491         pw.println("VolumeInfo{" + id + "}:");
492         pw.increaseIndent();
493         pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
494         pw.printPair("diskId", getDiskId());
495         pw.printPair("partGuid", partGuid);
496         pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
497         pw.printPair("mountUserId", mountUserId);
498         pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
499         pw.println();
500         pw.printPair("fsType", fsType);
501         pw.printPair("fsUuid", fsUuid);
502         pw.printPair("fsLabel", fsLabel);
503         pw.println();
504         pw.printPair("path", path);
505         pw.printPair("internalPath", internalPath);
506         pw.decreaseIndent();
507         pw.println();
508     }
509 
510     @Override
clone()511     public VolumeInfo clone() {
512         final Parcel temp = Parcel.obtain();
513         try {
514             writeToParcel(temp, 0);
515             temp.setDataPosition(0);
516             return CREATOR.createFromParcel(temp);
517         } finally {
518             temp.recycle();
519         }
520     }
521 
522     @Override
equals(Object o)523     public boolean equals(Object o) {
524         if (o instanceof VolumeInfo) {
525             return Objects.equals(id, ((VolumeInfo) o).id);
526         } else {
527             return false;
528         }
529     }
530 
531     @Override
hashCode()532     public int hashCode() {
533         return id.hashCode();
534     }
535 
536     @UnsupportedAppUsage
537     public static final @android.annotation.NonNull Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
538         @Override
539         public VolumeInfo createFromParcel(Parcel in) {
540             return new VolumeInfo(in);
541         }
542 
543         @Override
544         public VolumeInfo[] newArray(int size) {
545             return new VolumeInfo[size];
546         }
547     };
548 
549     @Override
describeContents()550     public int describeContents() {
551         return 0;
552     }
553 
554     @Override
writeToParcel(Parcel parcel, int flags)555     public void writeToParcel(Parcel parcel, int flags) {
556         parcel.writeString8(id);
557         parcel.writeInt(type);
558         if (disk != null) {
559             parcel.writeInt(1);
560             disk.writeToParcel(parcel, flags);
561         } else {
562             parcel.writeInt(0);
563         }
564         parcel.writeString8(partGuid);
565         parcel.writeInt(mountFlags);
566         parcel.writeInt(mountUserId);
567         parcel.writeInt(state);
568         parcel.writeString8(fsType);
569         parcel.writeString8(fsUuid);
570         parcel.writeString8(fsLabel);
571         parcel.writeString8(path);
572         parcel.writeString8(internalPath);
573     }
574 }
575