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