StorageVolume.java revision db892b84e513f174d29f67d293435407a0ac7c6c
1/*
2 * Copyright (C) 2011 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.net.TrafficStats;
24import android.net.Uri;
25import android.os.Environment;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.os.UserHandle;
29import android.provider.DocumentsContract;
30
31import com.android.internal.util.IndentingPrintWriter;
32import com.android.internal.util.Preconditions;
33
34import java.io.CharArrayWriter;
35import java.io.File;
36
37/**
38 * Information about a shared/external storage volume for a specific user.
39 *
40 * <p>
41 * A device always has one (and one only) primary storage volume, but it could have extra volumes,
42 * like SD cards and USB drives. This object represents the logical view of a storage
43 * volume for a specific user: different users might have different views for the same physical
44 * volume (for example, if the volume is a built-in emulated storage).
45 *
46 * <p>
47 * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
48 * verify its state.
49 *
50 * <p>
51 * Applications willing to read or write to this storage volume needs to get a permission from the
52 * user first, which can be achieved in the following ways:
53 *
54 * <ul>
55 * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
56 * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
57 * simpler API and narrows the access to the given directory (and its descendants).
58 * <li>To get access to any directory (and its descendants), they can use the Storage Acess
59 * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
60 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
61 * select this specific volume.
62 * <li>To get read and write access to the primary storage volume, applications can declare the
63 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
64 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
65 * latter including the former. This approach is discouraged, since users may be hesitant to grant
66 * broad access to all files contained on a storage device.
67 * </ul>
68 *
69 * <p>It can be obtained through {@link StorageManager#getVolumeList()} and
70 * {@link StorageManager#getPrimaryVolume()} and also as an extra in some broadcasts
71 * (see {@link #EXTRA_STORAGE_VOLUME}).
72 *
73 * <p>
74 * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
75 * storage semantics.
76 */
77// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
78// user, but is now part of the public API.
79public final class StorageVolume implements Parcelable {
80
81    private final String mId;
82    private final int mStorageId;
83    private final File mPath;
84    private final String mDescription;
85    private final boolean mPrimary;
86    private final boolean mRemovable;
87    private final boolean mEmulated;
88    private final long mMtpReserveSize;
89    private final boolean mAllowMassStorage;
90    private final long mMaxFileSize;
91    private final UserHandle mOwner;
92    private final String mFsUuid;
93    private final String mState;
94
95    /**
96     * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
97     * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
98     * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
99     * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
100     * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
101     * contains a {@link StorageVolume}.
102     */
103    // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
104    public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
105
106    /**
107     * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
108     *
109     * @hide
110     */
111    public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
112
113    /**
114     * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
115     */
116    private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
117            "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
118
119    /** {@hide} */
120    public static final int STORAGE_ID_INVALID = 0x00000000;
121    /** {@hide} */
122    public static final int STORAGE_ID_PRIMARY = 0x00010001;
123
124    /** {@hide} */
125    public StorageVolume(String id, int storageId, File path, String description, boolean primary,
126            boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
127            long maxFileSize, UserHandle owner, String fsUuid, String state) {
128        mId = Preconditions.checkNotNull(id);
129        mStorageId = storageId;
130        mPath = Preconditions.checkNotNull(path);
131        mDescription = Preconditions.checkNotNull(description);
132        mPrimary = primary;
133        mRemovable = removable;
134        mEmulated = emulated;
135        mMtpReserveSize = mtpReserveSize;
136        mAllowMassStorage = allowMassStorage;
137        mMaxFileSize = maxFileSize;
138        mOwner = Preconditions.checkNotNull(owner);
139        mFsUuid = fsUuid;
140        mState = Preconditions.checkNotNull(state);
141    }
142
143    private StorageVolume(Parcel in) {
144        mId = in.readString();
145        mStorageId = in.readInt();
146        mPath = new File(in.readString());
147        mDescription = in.readString();
148        mPrimary = in.readInt() != 0;
149        mRemovable = in.readInt() != 0;
150        mEmulated = in.readInt() != 0;
151        mMtpReserveSize = in.readLong();
152        mAllowMassStorage = in.readInt() != 0;
153        mMaxFileSize = in.readLong();
154        mOwner = in.readParcelable(null);
155        mFsUuid = in.readString();
156        mState = in.readString();
157    }
158
159    /** {@hide} */
160    public String getId() {
161        return mId;
162    }
163
164    /**
165     * Returns the mount path for the volume.
166     *
167     * @return the mount path
168     * @hide
169     */
170    public String getPath() {
171        return mPath.toString();
172    }
173
174    /** {@hide} */
175    public File getPathFile() {
176        return mPath;
177    }
178
179    /**
180     * Returns a user-visible description of the volume.
181     *
182     * @return the volume description
183     */
184    public String getDescription(Context context) {
185        return mDescription;
186    }
187
188    /**
189     * Returns true if the volume is the primary shared/external storage, which is the volume
190     * backed by {@link Environment#getExternalStorageDirectory()}.
191     */
192    public boolean isPrimary() {
193        return mPrimary;
194    }
195
196    /**
197     * Returns true if the volume is removable.
198     *
199     * @return is removable
200     */
201    public boolean isRemovable() {
202        return mRemovable;
203    }
204
205    /**
206     * Returns true if the volume is emulated.
207     *
208     * @return is removable
209     */
210    public boolean isEmulated() {
211        return mEmulated;
212    }
213
214    /**
215     * Returns the MTP storage ID for the volume.
216     * this is also used for the storage_id column in the media provider.
217     *
218     * @return MTP storage ID
219     * @hide
220     */
221    public int getStorageId() {
222        return mStorageId;
223    }
224
225    /**
226     * Number of megabytes of space to leave unallocated by MTP.
227     * MTP will subtract this value from the free space it reports back
228     * to the host via GetStorageInfo, and will not allow new files to
229     * be added via MTP if there is less than this amount left free in the storage.
230     * If MTP has dedicated storage this value should be zero, but if MTP is
231     * sharing storage with the rest of the system, set this to a positive value
232     * to ensure that MTP activity does not result in the storage being
233     * too close to full.
234     *
235     * @return MTP reserve space
236     * @hide
237     */
238    public int getMtpReserveSpace() {
239        return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
240    }
241
242    /**
243     * Returns true if this volume can be shared via USB mass storage.
244     *
245     * @return whether mass storage is allowed
246     * @hide
247     */
248    public boolean allowMassStorage() {
249        return mAllowMassStorage;
250    }
251
252    /**
253     * Returns maximum file size for the volume, or zero if it is unbounded.
254     *
255     * @return maximum file size
256     * @hide
257     */
258    public long getMaxFileSize() {
259        return mMaxFileSize;
260    }
261
262    /** {@hide} */
263    public UserHandle getOwner() {
264        return mOwner;
265    }
266
267    /**
268     * Gets the volume UUID, if any.
269     */
270    public @Nullable String getUuid() {
271        return mFsUuid;
272    }
273
274    /**
275     * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
276     * parse or UUID is unknown.
277     * @hide
278     */
279    public int getFatVolumeId() {
280        if (mFsUuid == null || mFsUuid.length() != 9) {
281            return -1;
282        }
283        try {
284            return (int) Long.parseLong(mFsUuid.replace("-", ""), 16);
285        } catch (NumberFormatException e) {
286            return -1;
287        }
288    }
289
290    /** {@hide} */
291    public String getUserLabel() {
292        return mDescription;
293    }
294
295    /**
296     * Returns the current state of the volume.
297     *
298     * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
299     *         {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
300     *         {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
301     *         {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
302     *         {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
303     */
304    public String getState() {
305        return mState;
306    }
307
308    /**
309     * Builds an intent to give access to a standard storage directory or entire volume after
310     * obtaining the user's approval.
311     * <p>
312     * When invoked, the system will ask the user to grant access to the requested directory (and
313     * its descendants). The result of the request will be returned to the activity through the
314     * {@code onActivityResult} method.
315     * <p>
316     * To gain access to descendants (child, grandchild, etc) documents, use
317     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
318     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
319     *
320     * <b>If your application only needs to store internal data, consider using
321     * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
322     * {@link Context#getExternalCacheDirs()}, or
323     * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
324     *
325     * <strong>NOTE: </strong>requesting access to the entire volume is not recommended and it will
326     * result in a stronger message displayed to the user, which may cause the user to reject
327     * the request.
328     *
329     * @param directoryName must be one of
330     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
331     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
332     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
333     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
334     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}, or
335     * {code null} to request access to the entire volume.
336     *
337     * @see DocumentsContract
338     */
339    public Intent createAccessIntent(@NonNull String directoryName) {
340        final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
341        intent.putExtra(EXTRA_STORAGE_VOLUME, this);
342        intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
343        return intent;
344    }
345
346    @Override
347    public boolean equals(Object obj) {
348        if (obj instanceof StorageVolume && mPath != null) {
349            StorageVolume volume = (StorageVolume)obj;
350            return (mPath.equals(volume.mPath));
351        }
352        return false;
353    }
354
355    @Override
356    public int hashCode() {
357        return mPath.hashCode();
358    }
359
360    @Override
361    public String toString() {
362        final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
363        if (mFsUuid != null) {
364            buffer.append(" (").append(mFsUuid).append(")");
365        }
366        return buffer.toString();
367    }
368
369    /** {@hide} */
370    // TODO(b/26742218): find out where toString() is called internally and replace these calls by
371    // dump().
372    public String dump() {
373        final CharArrayWriter writer = new CharArrayWriter();
374        dump(new IndentingPrintWriter(writer, "    ", 80));
375        return writer.toString();
376    }
377
378    /** {@hide} */
379    public void dump(IndentingPrintWriter pw) {
380        pw.println("StorageVolume:");
381        pw.increaseIndent();
382        pw.printPair("mId", mId);
383        pw.printPair("mStorageId", mStorageId);
384        pw.printPair("mPath", mPath);
385        pw.printPair("mDescription", mDescription);
386        pw.printPair("mPrimary", mPrimary);
387        pw.printPair("mRemovable", mRemovable);
388        pw.printPair("mEmulated", mEmulated);
389        pw.printPair("mMtpReserveSize", mMtpReserveSize);
390        pw.printPair("mAllowMassStorage", mAllowMassStorage);
391        pw.printPair("mMaxFileSize", mMaxFileSize);
392        pw.printPair("mOwner", mOwner);
393        pw.printPair("mFsUuid", mFsUuid);
394        pw.printPair("mState", mState);
395        pw.decreaseIndent();
396    }
397
398    public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
399        @Override
400        public StorageVolume createFromParcel(Parcel in) {
401            return new StorageVolume(in);
402        }
403
404        @Override
405        public StorageVolume[] newArray(int size) {
406            return new StorageVolume[size];
407        }
408    };
409
410    @Override
411    public int describeContents() {
412        return 0;
413    }
414
415    @Override
416    public void writeToParcel(Parcel parcel, int flags) {
417        parcel.writeString(mId);
418        parcel.writeInt(mStorageId);
419        parcel.writeString(mPath.toString());
420        parcel.writeString(mDescription);
421        parcel.writeInt(mPrimary ? 1 : 0);
422        parcel.writeInt(mRemovable ? 1 : 0);
423        parcel.writeInt(mEmulated ? 1 : 0);
424        parcel.writeLong(mMtpReserveSize);
425        parcel.writeInt(mAllowMassStorage ? 1 : 0);
426        parcel.writeLong(mMaxFileSize);
427        parcel.writeParcelable(mOwner, flags);
428        parcel.writeString(mFsUuid);
429        parcel.writeString(mState);
430    }
431}
432