VolumeInfo.java revision d95d3bfb2b28a4f21f3fdcd740160c9a61eb0363
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;
41
42/**
43 * Information about a storage volume that may be mounted. A volume may be a
44 * partition on a physical {@link DiskInfo}, an emulated volume above some other
45 * storage medium, or a standalone container like an ASEC or OBB.
46 *
47 * @hide
48 */
49public class VolumeInfo implements Parcelable {
50    public static final String EXTRA_VOLUME_ID = "android.os.storage.extra.VOLUME_ID";
51
52    /** Stub volume representing internal private storage */
53    public static final String ID_PRIVATE_INTERNAL = "private";
54    /** Real volume representing internal emulated storage */
55    public static final String ID_EMULATED_INTERNAL = "emulated";
56
57    public static final int TYPE_PUBLIC = 0;
58    public static final int TYPE_PRIVATE = 1;
59    public static final int TYPE_EMULATED = 2;
60    public static final int TYPE_ASEC = 3;
61    public static final int TYPE_OBB = 4;
62
63    public static final int STATE_UNMOUNTED = 0;
64    public static final int STATE_MOUNTING = 1;
65    public static final int STATE_MOUNTED = 2;
66    public static final int STATE_FORMATTING = 3;
67    public static final int STATE_UNMOUNTING = 4;
68    public static final int STATE_UNMOUNTABLE = 5;
69    public static final int STATE_REMOVED = 6;
70
71    public static final int FLAG_PRIMARY = 1 << 0;
72    public static final int FLAG_VISIBLE = 1 << 1;
73
74    public static final int USER_FLAG_INITED = 1 << 0;
75    public static final int USER_FLAG_SNOOZED = 1 << 1;
76
77    private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
78    private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
79
80    static {
81        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
82        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING);
83        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
84        sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
85        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
86        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
87        sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
88
89        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
90        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
91        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
92        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
93        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
94        sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
95    }
96
97    /** vold state */
98    public final String id;
99    public final int type;
100    public int flags = 0;
101    public int userId = -1;
102    public int state = STATE_UNMOUNTED;
103    public String fsType;
104    public String fsUuid;
105    public String fsLabel;
106    public String path;
107
108    /** Framework state */
109    public final int mtpIndex;
110    public String diskId;
111    public String nickname;
112    public int userFlags = 0;
113
114    public VolumeInfo(String id, int type, int mtpIndex) {
115        this.id = Preconditions.checkNotNull(id);
116        this.type = type;
117        this.mtpIndex = mtpIndex;
118    }
119
120    public VolumeInfo(Parcel parcel) {
121        id = parcel.readString();
122        type = parcel.readInt();
123        flags = parcel.readInt();
124        userId = parcel.readInt();
125        state = parcel.readInt();
126        fsType = parcel.readString();
127        fsUuid = parcel.readString();
128        fsLabel = parcel.readString();
129        path = parcel.readString();
130        mtpIndex = parcel.readInt();
131        diskId = parcel.readString();
132        nickname = parcel.readString();
133        userFlags = parcel.readInt();
134    }
135
136    public static @NonNull String getEnvironmentForState(int state) {
137        final String envState = sStateToEnvironment.get(state);
138        if (envState != null) {
139            return envState;
140        } else {
141            return Environment.MEDIA_UNKNOWN;
142        }
143    }
144
145    public static @Nullable String getBroadcastForEnvironment(String envState) {
146        return sEnvironmentToBroadcast.get(envState);
147    }
148
149    public static @Nullable String getBroadcastForState(int state) {
150        return getBroadcastForEnvironment(getEnvironmentForState(state));
151    }
152
153    public @NonNull String getId() {
154        return id;
155    }
156
157    public @Nullable String getDiskId() {
158        return diskId;
159    }
160
161    public int getType() {
162        return type;
163    }
164
165    public int getState() {
166        return state;
167    }
168
169    public @Nullable String getFsUuid() {
170        return fsUuid;
171    }
172
173    public @Nullable String getNickname() {
174        return nickname;
175    }
176
177    public @Nullable String getDescription() {
178        if (ID_PRIVATE_INTERNAL.equals(id)) {
179            return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
180        } else if (!TextUtils.isEmpty(nickname)) {
181            return nickname;
182        } else if (!TextUtils.isEmpty(fsLabel)) {
183            return fsLabel;
184        } else {
185            return null;
186        }
187    }
188
189    public boolean isPrimary() {
190        return (flags & FLAG_PRIMARY) != 0;
191    }
192
193    public boolean isVisible() {
194        return (flags & FLAG_VISIBLE) != 0;
195    }
196
197    public boolean isInited() {
198        return (userFlags & USER_FLAG_INITED) != 0;
199    }
200
201    public boolean isSnoozed() {
202        return (userFlags & USER_FLAG_SNOOZED) != 0;
203    }
204
205    public boolean isVisibleToUser(int userId) {
206        if (type == TYPE_PUBLIC && userId == this.userId) {
207            return isVisible();
208        } else if (type == TYPE_EMULATED) {
209            return isVisible();
210        } else {
211            return false;
212        }
213    }
214
215    public File getPath() {
216        return new File(path);
217    }
218
219    public File getPathForUser(int userId) {
220        if (path == null) {
221            return null;
222        } else if (type == TYPE_PUBLIC && userId == this.userId) {
223            return new File(path);
224        } else if (type == TYPE_EMULATED) {
225            return new File(path, Integer.toString(userId));
226        } else {
227            return null;
228        }
229    }
230
231    public StorageVolume buildStorageVolume(Context context, int userId) {
232        final boolean removable;
233        final boolean emulated;
234        final boolean allowMassStorage = false;
235        final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
236        final String envState = getEnvironmentForState(state);
237
238        File userPath = getPathForUser(userId);
239        if (userPath == null) {
240            userPath = new File("/dev/null");
241        }
242
243        String description = getDescription();
244        if (description == null) {
245            description = context.getString(android.R.string.unknownName);
246        }
247
248        long mtpReserveSize = 0;
249        long maxFileSize = 0;
250
251        if (type == TYPE_EMULATED) {
252            emulated = true;
253            mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath);
254
255            if (ID_EMULATED_INTERNAL.equals(id)) {
256                removable = false;
257            } else {
258                removable = true;
259            }
260
261        } else if (type == TYPE_PUBLIC) {
262            emulated = false;
263            removable = true;
264
265            if ("vfat".equals(fsType)) {
266                maxFileSize = 4294967295L;
267            }
268
269        } else {
270            throw new IllegalStateException("Unexpected volume type " + type);
271        }
272
273        return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
274                emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
275                fsUuid, envState);
276    }
277
278    // TODO: avoid this layering violation
279    private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
280    private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
281
282    /**
283     * Build an intent to browse the contents of this volume. Only valid for
284     * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
285     */
286    public Intent buildBrowseIntent() {
287        final Uri uri;
288        if (type == VolumeInfo.TYPE_PUBLIC) {
289            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
290        } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(id)) {
291            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
292                    DOCUMENT_ROOT_PRIMARY_EMULATED);
293        } else if (type == VolumeInfo.TYPE_EMULATED) {
294            // TODO: build intent once supported
295            uri = null;
296        } else {
297            throw new IllegalArgumentException();
298        }
299
300        final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
301        intent.addCategory(Intent.CATEGORY_DEFAULT);
302        intent.setData(uri);
303        return intent;
304    }
305
306    @Override
307    public String toString() {
308        final CharArrayWriter writer = new CharArrayWriter();
309        dump(new IndentingPrintWriter(writer, "    ", 80));
310        return writer.toString();
311    }
312
313    public void dump(IndentingPrintWriter pw) {
314        pw.println("VolumeInfo:");
315        pw.increaseIndent();
316        pw.printPair("id", id);
317        pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
318        pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
319        pw.printPair("userId", userId);
320        pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
321        pw.println();
322        pw.printPair("fsType", fsType);
323        pw.printPair("fsUuid", fsUuid);
324        pw.printPair("fsLabel", fsLabel);
325        pw.println();
326        pw.printPair("path", path);
327        pw.printPair("mtpIndex", mtpIndex);
328        pw.printPair("diskId", diskId);
329        pw.printPair("nickname", nickname);
330        pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags));
331        pw.decreaseIndent();
332        pw.println();
333    }
334
335    @Override
336    public VolumeInfo clone() {
337        final Parcel temp = Parcel.obtain();
338        try {
339            writeToParcel(temp, 0);
340            temp.setDataPosition(0);
341            return CREATOR.createFromParcel(temp);
342        } finally {
343            temp.recycle();
344        }
345    }
346
347    public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
348        @Override
349        public VolumeInfo createFromParcel(Parcel in) {
350            return new VolumeInfo(in);
351        }
352
353        @Override
354        public VolumeInfo[] newArray(int size) {
355            return new VolumeInfo[size];
356        }
357    };
358
359    @Override
360    public int describeContents() {
361        return 0;
362    }
363
364    @Override
365    public void writeToParcel(Parcel parcel, int flags) {
366        parcel.writeString(id);
367        parcel.writeInt(type);
368        parcel.writeInt(this.flags);
369        parcel.writeInt(userId);
370        parcel.writeInt(state);
371        parcel.writeString(fsType);
372        parcel.writeString(fsUuid);
373        parcel.writeString(fsLabel);
374        parcel.writeString(path);
375        parcel.writeInt(mtpIndex);
376        parcel.writeString(diskId);
377        parcel.writeString(nickname);
378        parcel.writeInt(userFlags);
379    }
380}
381