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