VolumeInfo.java revision 27de30d31c3e79bc429cb71aed9681c55243f18d
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
17package android.os.storage;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.Resources;
24import android.mtp.MtpStorage;
25import android.net.Uri;
26import android.os.Environment;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.os.UserHandle;
30import android.provider.DocumentsContract;
31import android.text.TextUtils;
32import android.util.ArrayMap;
33import android.util.DebugUtils;
34import android.util.SparseArray;
35
36import com.android.internal.util.IndentingPrintWriter;
37import com.android.internal.util.Preconditions;
38
39import java.io.CharArrayWriter;
40import java.io.File;
41import java.util.Comparator;
42import java.util.Objects;
43
44/**
45 * Information about a storage volume that may be mounted. A volume may be a
46 * partition on a physical {@link DiskInfo}, an emulated volume above some other
47 * storage medium, or a standalone container like an ASEC or OBB.
48 *
49 * @hide
50 */
51public class VolumeInfo implements Parcelable {
52    public static final String EXTRA_VOLUME_ID = "android.os.storage.extra.VOLUME_ID";
53
54    /** Stub volume representing internal private storage */
55    public static final String ID_PRIVATE_INTERNAL = "private";
56    /** Real volume representing internal emulated storage */
57    public static final String ID_EMULATED_INTERNAL = "emulated";
58
59    public static final int TYPE_PUBLIC = 0;
60    public static final int TYPE_PRIVATE = 1;
61    public static final int TYPE_EMULATED = 2;
62    public static final int TYPE_ASEC = 3;
63    public static final int TYPE_OBB = 4;
64
65    public static final int STATE_UNMOUNTED = 0;
66    public static final int STATE_CHECKING = 1;
67    public static final int STATE_MOUNTED = 2;
68    public static final int STATE_MOUNTED_READ_ONLY = 3;
69    public static final int STATE_FORMATTING = 4;
70    public static final int STATE_EJECTING = 5;
71    public static final int STATE_UNMOUNTABLE = 6;
72    public static final int STATE_REMOVED = 7;
73    public static final int STATE_BAD_REMOVAL = 8;
74
75    public static final int MOUNT_FLAG_PRIMARY = 1 << 0;
76    public static final int MOUNT_FLAG_VISIBLE = 1 << 1;
77
78    public static final int USER_FLAG_INITED = 1 << 0;
79    public static final int USER_FLAG_SNOOZED = 1 << 1;
80
81    private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
82    private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
83
84    private static final Comparator<VolumeInfo>
85            sDescriptionComparator = new Comparator<VolumeInfo>() {
86        @Override
87        public int compare(VolumeInfo lhs, VolumeInfo rhs) {
88            if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) {
89                return -1;
90            } else if (lhs.getDescription() == null) {
91                return 1;
92            } else if (rhs.getDescription() == null) {
93                return -1;
94            } else {
95                return lhs.getDescription().compareTo(rhs.getDescription());
96            }
97        }
98    };
99
100    static {
101        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
102        sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
103        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
104        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY);
105        sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
106        sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
107        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
108        sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
109        sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL);
110
111        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
112        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
113        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
114        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED);
115        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
116        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
117        sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
118        sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL);
119    }
120
121    /** vold state */
122    public final String id;
123    public final int type;
124    public final DiskInfo disk;
125    public int mountFlags = 0;
126    public int mountUserId = -1;
127    public int state = STATE_UNMOUNTED;
128    public String fsType;
129    public String fsUuid;
130    public String fsLabel;
131    public String path;
132
133    /** Framework state */
134    public final int mtpIndex;
135    public String nickname;
136    public int userFlags = 0;
137
138    public VolumeInfo(String id, int type, DiskInfo disk, int mtpIndex) {
139        this.id = Preconditions.checkNotNull(id);
140        this.type = type;
141        this.disk = disk;
142        this.mtpIndex = mtpIndex;
143    }
144
145    public VolumeInfo(Parcel parcel) {
146        id = parcel.readString();
147        type = parcel.readInt();
148        if (parcel.readInt() != 0) {
149            disk = DiskInfo.CREATOR.createFromParcel(parcel);
150        } else {
151            disk = null;
152        }
153        mountFlags = parcel.readInt();
154        mountUserId = parcel.readInt();
155        state = parcel.readInt();
156        fsType = parcel.readString();
157        fsUuid = parcel.readString();
158        fsLabel = parcel.readString();
159        path = parcel.readString();
160        mtpIndex = parcel.readInt();
161        nickname = parcel.readString();
162        userFlags = parcel.readInt();
163    }
164
165    public static @NonNull String getEnvironmentForState(int state) {
166        final String envState = sStateToEnvironment.get(state);
167        if (envState != null) {
168            return envState;
169        } else {
170            return Environment.MEDIA_UNKNOWN;
171        }
172    }
173
174    public static @Nullable String getBroadcastForEnvironment(String envState) {
175        return sEnvironmentToBroadcast.get(envState);
176    }
177
178    public static @Nullable String getBroadcastForState(int state) {
179        return getBroadcastForEnvironment(getEnvironmentForState(state));
180    }
181
182    public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() {
183        return sDescriptionComparator;
184    }
185
186    public @NonNull String getId() {
187        return id;
188    }
189
190    public @Nullable DiskInfo getDisk() {
191        return disk;
192    }
193
194    public @Nullable String getDiskId() {
195        return (disk != null) ? disk.id : null;
196    }
197
198    public int getType() {
199        return type;
200    }
201
202    public int getState() {
203        return state;
204    }
205
206    public @Nullable String getFsUuid() {
207        return fsUuid;
208    }
209
210    public @Nullable String getNickname() {
211        return nickname;
212    }
213
214    public int getMountUserId() {
215        return mountUserId;
216    }
217
218    public @Nullable String getDescription() {
219        if (ID_PRIVATE_INTERNAL.equals(id)) {
220            return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
221        } else if (!TextUtils.isEmpty(nickname)) {
222            return nickname;
223        } else if (!TextUtils.isEmpty(fsLabel)) {
224            return fsLabel;
225        } else {
226            return null;
227        }
228    }
229
230    public boolean isMountedReadable() {
231        return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
232    }
233
234    public boolean isMountedWritable() {
235        return state == STATE_MOUNTED;
236    }
237
238    public boolean isPrimary() {
239        return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
240    }
241
242    public boolean isVisible() {
243        return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
244    }
245
246    public boolean isInited() {
247        return (userFlags & USER_FLAG_INITED) != 0;
248    }
249
250    public boolean isSnoozed() {
251        return (userFlags & USER_FLAG_SNOOZED) != 0;
252    }
253
254    public boolean isVisibleToUser(int userId) {
255        if (type == TYPE_PUBLIC && userId == this.mountUserId) {
256            return isVisible();
257        } else if (type == TYPE_EMULATED) {
258            return isVisible();
259        } else {
260            return false;
261        }
262    }
263
264    public File getPath() {
265        return new File(path);
266    }
267
268    public File getPathForUser(int userId) {
269        if (path == null) {
270            return null;
271        } else if (type == TYPE_PUBLIC && userId == this.mountUserId) {
272            return new File(path);
273        } else if (type == TYPE_EMULATED) {
274            return new File(path, Integer.toString(userId));
275        } else {
276            return null;
277        }
278    }
279
280    /**
281     * Path which is accessible to apps holding
282     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
283     */
284    public File getInternalPathForUser(int userId) {
285        if (type == TYPE_PUBLIC) {
286            // TODO: plumb through cleaner path from vold
287            return new File(path.replace("/storage/", "/mnt/media_rw/"));
288        } else {
289            return getPathForUser(userId);
290        }
291    }
292
293    public StorageVolume buildStorageVolume(Context context, int userId) {
294        final boolean removable;
295        final boolean emulated;
296        final boolean allowMassStorage = false;
297        final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
298        final String envState = getEnvironmentForState(state);
299
300        File userPath = getPathForUser(userId);
301        if (userPath == null) {
302            userPath = new File("/dev/null");
303        }
304
305        String description = getDescription();
306        if (description == null) {
307            description = context.getString(android.R.string.unknownName);
308        }
309
310        long mtpReserveSize = 0;
311        long maxFileSize = 0;
312
313        if (type == TYPE_EMULATED) {
314            emulated = true;
315            mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath);
316
317            if (ID_EMULATED_INTERNAL.equals(id)) {
318                removable = false;
319            } else {
320                removable = true;
321            }
322
323        } else if (type == TYPE_PUBLIC) {
324            emulated = false;
325            removable = true;
326
327            if ("vfat".equals(fsType)) {
328                maxFileSize = 4294967295L;
329            }
330
331        } else {
332            throw new IllegalStateException("Unexpected volume type " + type);
333        }
334
335        return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
336                emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
337                fsUuid, envState);
338    }
339
340    // TODO: avoid this layering violation
341    private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
342    private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
343
344    /**
345     * Build an intent to browse the contents of this volume. Only valid for
346     * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
347     */
348    public Intent buildBrowseIntent() {
349        final Uri uri;
350        if (type == VolumeInfo.TYPE_PUBLIC) {
351            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
352        } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(id)) {
353            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
354                    DOCUMENT_ROOT_PRIMARY_EMULATED);
355        } else if (type == VolumeInfo.TYPE_EMULATED) {
356            // TODO: build intent once supported
357            uri = null;
358        } else {
359            throw new IllegalArgumentException();
360        }
361
362        final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
363        intent.addCategory(Intent.CATEGORY_DEFAULT);
364        intent.setData(uri);
365        return intent;
366    }
367
368    @Override
369    public String toString() {
370        final CharArrayWriter writer = new CharArrayWriter();
371        dump(new IndentingPrintWriter(writer, "    ", 80));
372        return writer.toString();
373    }
374
375    public void dump(IndentingPrintWriter pw) {
376        pw.println("VolumeInfo{" + id + "}:");
377        pw.increaseIndent();
378        pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
379        pw.printPair("diskId", getDiskId());
380        pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
381        pw.printPair("mountUserId", mountUserId);
382        pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
383        pw.println();
384        pw.printPair("fsType", fsType);
385        pw.printPair("fsUuid", fsUuid);
386        pw.printPair("fsLabel", fsLabel);
387        pw.println();
388        pw.printPair("path", path);
389        pw.printPair("mtpIndex", mtpIndex);
390        pw.printPair("nickname", nickname);
391        pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags));
392        pw.decreaseIndent();
393        pw.println();
394    }
395
396    @Override
397    public VolumeInfo clone() {
398        final Parcel temp = Parcel.obtain();
399        try {
400            writeToParcel(temp, 0);
401            temp.setDataPosition(0);
402            return CREATOR.createFromParcel(temp);
403        } finally {
404            temp.recycle();
405        }
406    }
407
408    @Override
409    public boolean equals(Object o) {
410        if (o instanceof VolumeInfo) {
411            return Objects.equals(id, ((VolumeInfo) o).id);
412        } else {
413            return false;
414        }
415    }
416
417    @Override
418    public int hashCode() {
419        return id.hashCode();
420    }
421
422    public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
423        @Override
424        public VolumeInfo createFromParcel(Parcel in) {
425            return new VolumeInfo(in);
426        }
427
428        @Override
429        public VolumeInfo[] newArray(int size) {
430            return new VolumeInfo[size];
431        }
432    };
433
434    @Override
435    public int describeContents() {
436        return 0;
437    }
438
439    @Override
440    public void writeToParcel(Parcel parcel, int flags) {
441        parcel.writeString(id);
442        parcel.writeInt(type);
443        if (disk != null) {
444            parcel.writeInt(1);
445            disk.writeToParcel(parcel, flags);
446        } else {
447            parcel.writeInt(0);
448        }
449        parcel.writeInt(mountFlags);
450        parcel.writeInt(mountUserId);
451        parcel.writeInt(state);
452        parcel.writeString(fsType);
453        parcel.writeString(fsUuid);
454        parcel.writeString(fsLabel);
455        parcel.writeString(path);
456        parcel.writeInt(mtpIndex);
457        parcel.writeString(nickname);
458        parcel.writeInt(userFlags);
459    }
460}
461