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