VolumeInfo.java revision 56bd3129138b525b0f2eba52bd4fa140f23e792c
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    private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
75    private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
76
77    static {
78        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
79        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING);
80        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
81        sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
82        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
83        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
84        sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
85
86        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
87        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
88        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
89        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
90        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
91        sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
92    }
93
94    /** vold state */
95    public final String id;
96    public final int type;
97    public int flags = 0;
98    public int userId = -1;
99    public int state = STATE_UNMOUNTED;
100    public String fsType;
101    public String fsUuid;
102    public String fsLabel;
103    public String path;
104
105    /** Framework state */
106    public final int mtpIndex;
107    public String nickname;
108    public String diskId;
109
110    public VolumeInfo(String id, int type, int mtpIndex) {
111        this.id = Preconditions.checkNotNull(id);
112        this.type = type;
113        this.mtpIndex = mtpIndex;
114    }
115
116    public VolumeInfo(Parcel parcel) {
117        id = parcel.readString();
118        type = parcel.readInt();
119        flags = parcel.readInt();
120        userId = parcel.readInt();
121        state = parcel.readInt();
122        fsType = parcel.readString();
123        fsUuid = parcel.readString();
124        fsLabel = parcel.readString();
125        path = parcel.readString();
126        mtpIndex = parcel.readInt();
127        nickname = parcel.readString();
128        diskId = parcel.readString();
129    }
130
131    public static @NonNull String getEnvironmentForState(int state) {
132        final String envState = sStateToEnvironment.get(state);
133        if (envState != null) {
134            return envState;
135        } else {
136            return Environment.MEDIA_UNKNOWN;
137        }
138    }
139
140    public static @Nullable String getBroadcastForEnvironment(String envState) {
141        return sEnvironmentToBroadcast.get(envState);
142    }
143
144    public static @Nullable String getBroadcastForState(int state) {
145        return getBroadcastForEnvironment(getEnvironmentForState(state));
146    }
147
148    public @Nullable String getDescription() {
149        if (ID_PRIVATE_INTERNAL.equals(id)) {
150            return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
151        } else if (!TextUtils.isEmpty(nickname)) {
152            return nickname;
153        } else if (!TextUtils.isEmpty(fsLabel)) {
154            return fsLabel;
155        } else {
156            return null;
157        }
158    }
159
160    public boolean isPrimary() {
161        return (flags & FLAG_PRIMARY) != 0;
162    }
163
164    public boolean isVisible() {
165        return (flags & FLAG_VISIBLE) != 0;
166    }
167
168    public boolean isVisibleToUser(int userId) {
169        if (type == TYPE_PUBLIC && userId == this.userId) {
170            return isVisible();
171        } else if (type == TYPE_EMULATED) {
172            return isVisible();
173        } else {
174            return false;
175        }
176    }
177
178    public File getPathForUser(int userId) {
179        if (path == null) {
180            return null;
181        } else if (type == TYPE_PUBLIC && userId == this.userId) {
182            return new File(path);
183        } else if (type == TYPE_EMULATED) {
184            return new File(path, Integer.toString(userId));
185        } else {
186            return null;
187        }
188    }
189
190    public StorageVolume buildStorageVolume(Context context, int userId) {
191        final boolean removable;
192        final boolean emulated;
193        final boolean allowMassStorage = false;
194        final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
195        final String envState = getEnvironmentForState(state);
196
197        File userPath = getPathForUser(userId);
198        if (userPath == null) {
199            userPath = new File("/dev/null");
200        }
201
202        String description = getDescription();
203        if (description == null) {
204            description = context.getString(android.R.string.unknownName);
205        }
206
207        long mtpReserveSize = 0;
208        long maxFileSize = 0;
209
210        if (type == TYPE_EMULATED) {
211            emulated = true;
212            mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath);
213
214            if (ID_EMULATED_INTERNAL.equals(id)) {
215                removable = false;
216            } else {
217                removable = true;
218            }
219
220        } else if (type == TYPE_PUBLIC) {
221            emulated = false;
222            removable = true;
223
224            if ("vfat".equals(fsType)) {
225                maxFileSize = 4294967295L;
226            }
227
228        } else {
229            throw new IllegalStateException("Unexpected volume type " + type);
230        }
231
232        return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
233                emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
234                fsUuid, envState);
235    }
236
237    // TODO: avoid this layering violation
238    private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
239    private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
240
241    /**
242     * Build an intent to browse the contents of this volume. Only valid for
243     * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
244     */
245    public Intent buildBrowseIntent() {
246        final Uri uri;
247        if (type == VolumeInfo.TYPE_PUBLIC) {
248            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
249        } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(id)) {
250            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
251                    DOCUMENT_ROOT_PRIMARY_EMULATED);
252        } else if (type == VolumeInfo.TYPE_EMULATED) {
253            // TODO: build intent once supported
254            uri = null;
255        } else {
256            throw new IllegalArgumentException();
257        }
258
259        final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
260        intent.addCategory(Intent.CATEGORY_DEFAULT);
261        intent.setData(uri);
262        return intent;
263    }
264
265    @Override
266    public String toString() {
267        final CharArrayWriter writer = new CharArrayWriter();
268        dump(new IndentingPrintWriter(writer, "    ", 80));
269        return writer.toString();
270    }
271
272    public void dump(IndentingPrintWriter pw) {
273        pw.println("VolumeInfo:");
274        pw.increaseIndent();
275        pw.printPair("id", id);
276        pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
277        pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
278        pw.printPair("userId", userId);
279        pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
280        pw.println();
281        pw.printPair("fsType", fsType);
282        pw.printPair("fsUuid", fsUuid);
283        pw.printPair("fsLabel", fsLabel);
284        pw.println();
285        pw.printPair("path", path);
286        pw.printPair("mtpIndex", mtpIndex);
287        pw.printPair("nickname", nickname);
288        pw.printPair("diskId", diskId);
289        pw.decreaseIndent();
290        pw.println();
291    }
292
293    @Override
294    public VolumeInfo clone() {
295        final Parcel temp = Parcel.obtain();
296        try {
297            writeToParcel(temp, 0);
298            temp.setDataPosition(0);
299            return CREATOR.createFromParcel(temp);
300        } finally {
301            temp.recycle();
302        }
303    }
304
305    public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
306        @Override
307        public VolumeInfo createFromParcel(Parcel in) {
308            return new VolumeInfo(in);
309        }
310
311        @Override
312        public VolumeInfo[] newArray(int size) {
313            return new VolumeInfo[size];
314        }
315    };
316
317    @Override
318    public int describeContents() {
319        return 0;
320    }
321
322    @Override
323    public void writeToParcel(Parcel parcel, int flags) {
324        parcel.writeString(id);
325        parcel.writeInt(type);
326        parcel.writeInt(this.flags);
327        parcel.writeInt(userId);
328        parcel.writeInt(state);
329        parcel.writeString(fsType);
330        parcel.writeString(fsUuid);
331        parcel.writeString(fsLabel);
332        parcel.writeString(path);
333        parcel.writeInt(mtpIndex);
334        parcel.writeString(nickname);
335        parcel.writeString(diskId);
336    }
337}
338