OSDN Git Service

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