• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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