StorageVolume.java revision 71938e18ca4ad77519da70565710ef37e79443f8
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 File mInternalPath;
82    private final String mDescription;
83    private final boolean mPrimary;
84    private final boolean mRemovable;
85    private final boolean mEmulated;
86    private final boolean mAllowMassStorage;
87    private final long mMaxFileSize;
88    private final UserHandle mOwner;
89    private final String mFsUuid;
90    private final String mState;
91
92    /**
93     * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
94     * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
95     * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
96     * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
97     * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
98     * contains a {@link StorageVolume}.
99     */
100    // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
101    public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
102
103    /**
104     * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
105     *
106     * @hide
107     */
108    public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
109
110    /**
111     * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
112     */
113    private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
114            "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
115
116    /** {@hide} */
117    public static final int STORAGE_ID_INVALID = 0x00000000;
118    /** {@hide} */
119    public static final int STORAGE_ID_PRIMARY = 0x00010001;
120
121    /** {@hide} */
122    public StorageVolume(String id, File path, File internalPath, String description,
123            boolean primary, boolean removable, boolean emulated, boolean allowMassStorage,
124            long maxFileSize, UserHandle owner, String fsUuid, String state) {
125        mId = Preconditions.checkNotNull(id);
126        mPath = Preconditions.checkNotNull(path);
127        mInternalPath = Preconditions.checkNotNull(internalPath);
128        mDescription = Preconditions.checkNotNull(description);
129        mPrimary = primary;
130        mRemovable = removable;
131        mEmulated = emulated;
132        mAllowMassStorage = allowMassStorage;
133        mMaxFileSize = maxFileSize;
134        mOwner = Preconditions.checkNotNull(owner);
135        mFsUuid = fsUuid;
136        mState = Preconditions.checkNotNull(state);
137    }
138
139    private StorageVolume(Parcel in) {
140        mId = in.readString();
141        mPath = new File(in.readString());
142        mInternalPath = new File(in.readString());
143        mDescription = in.readString();
144        mPrimary = in.readInt() != 0;
145        mRemovable = in.readInt() != 0;
146        mEmulated = in.readInt() != 0;
147        mAllowMassStorage = in.readInt() != 0;
148        mMaxFileSize = in.readLong();
149        mOwner = in.readParcelable(null);
150        mFsUuid = in.readString();
151        mState = in.readString();
152    }
153
154    /** {@hide} */
155    public String getId() {
156        return mId;
157    }
158
159    /**
160     * Returns the mount path for the volume.
161     *
162     * @return the mount path
163     * @hide
164     */
165    public String getPath() {
166        return mPath.toString();
167    }
168
169    /**
170     * Returns the path of the underlying filesystem.
171     *
172     * @return the internal path
173     * @hide
174     */
175    public String getInternalPath() {
176        return mInternalPath.toString();
177    }
178
179    /** {@hide} */
180    public File getPathFile() {
181        return mPath;
182    }
183
184    /**
185     * Returns a user-visible description of the volume.
186     *
187     * @return the volume description
188     */
189    public String getDescription(Context context) {
190        return mDescription;
191    }
192
193    /**
194     * Returns true if the volume is the primary shared/external storage, which is the volume
195     * backed by {@link Environment#getExternalStorageDirectory()}.
196     */
197    public boolean isPrimary() {
198        return mPrimary;
199    }
200
201    /**
202     * Returns true if the volume is removable.
203     *
204     * @return is removable
205     */
206    public boolean isRemovable() {
207        return mRemovable;
208    }
209
210    /**
211     * Returns true if the volume is emulated.
212     *
213     * @return is removable
214     */
215    public boolean isEmulated() {
216        return mEmulated;
217    }
218
219    /**
220     * Returns true if this volume can be shared via USB mass storage.
221     *
222     * @return whether mass storage is allowed
223     * @hide
224     */
225    public boolean allowMassStorage() {
226        return mAllowMassStorage;
227    }
228
229    /**
230     * Returns maximum file size for the volume, or zero if it is unbounded.
231     *
232     * @return maximum file size
233     * @hide
234     */
235    public long getMaxFileSize() {
236        return mMaxFileSize;
237    }
238
239    /** {@hide} */
240    public UserHandle getOwner() {
241        return mOwner;
242    }
243
244    /**
245     * Gets the volume UUID, if any.
246     */
247    public @Nullable String getUuid() {
248        return mFsUuid;
249    }
250
251    /**
252     * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
253     * parse or UUID is unknown.
254     * @hide
255     */
256    public int getFatVolumeId() {
257        if (mFsUuid == null || mFsUuid.length() != 9) {
258            return -1;
259        }
260        try {
261            return (int) Long.parseLong(mFsUuid.replace("-", ""), 16);
262        } catch (NumberFormatException e) {
263            return -1;
264        }
265    }
266
267    /** {@hide} */
268    public String getUserLabel() {
269        return mDescription;
270    }
271
272    /**
273     * Returns the current state of the volume.
274     *
275     * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
276     *         {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
277     *         {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
278     *         {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
279     *         {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
280     */
281    public String getState() {
282        return mState;
283    }
284
285    /**
286     * Builds an intent to give access to a standard storage directory or entire volume after
287     * obtaining the user's approval.
288     * <p>
289     * When invoked, the system will ask the user to grant access to the requested directory (and
290     * its descendants). The result of the request will be returned to the activity through the
291     * {@code onActivityResult} method.
292     * <p>
293     * To gain access to descendants (child, grandchild, etc) documents, use
294     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
295     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
296     * <p>
297     * If your application only needs to store internal data, consider using
298     * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
299     * {@link Context#getExternalCacheDirs()}, or {@link Context#getExternalMediaDirs()}, which
300     * require no permissions to read or write.
301     * <p>
302     * Access to the entire volume is only available for non-primary volumes (for the primary
303     * volume, apps can use the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
304     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions) and should be used
305     * with caution, since users are more likely to deny access when asked for entire volume access
306     * rather than specific directories.
307     *
308     * @param directoryName must be one of {@link Environment#DIRECTORY_MUSIC},
309     *            {@link Environment#DIRECTORY_PODCASTS}, {@link Environment#DIRECTORY_RINGTONES},
310     *            {@link Environment#DIRECTORY_ALARMS}, {@link Environment#DIRECTORY_NOTIFICATIONS},
311     *            {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES},
312     *            {@link Environment#DIRECTORY_DOWNLOADS}, {@link Environment#DIRECTORY_DCIM}, or
313     *            {@link Environment#DIRECTORY_DOCUMENTS}, or {@code null} to request access to the
314     *            entire volume.
315     * @return intent to request access, or {@code null} if the requested directory is invalid for
316     *         that volume.
317     * @see DocumentsContract
318     */
319    public @Nullable Intent createAccessIntent(String directoryName) {
320        if ((isPrimary() && directoryName == null) ||
321                (directoryName != null && !Environment.isStandardDirectory(directoryName))) {
322            return null;
323        }
324        final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
325        intent.putExtra(EXTRA_STORAGE_VOLUME, this);
326        intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
327        return intent;
328    }
329
330    @Override
331    public boolean equals(Object obj) {
332        if (obj instanceof StorageVolume && mPath != null) {
333            StorageVolume volume = (StorageVolume)obj;
334            return (mPath.equals(volume.mPath));
335        }
336        return false;
337    }
338
339    @Override
340    public int hashCode() {
341        return mPath.hashCode();
342    }
343
344    @Override
345    public String toString() {
346        final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
347        if (mFsUuid != null) {
348            buffer.append(" (").append(mFsUuid).append(")");
349        }
350        return buffer.toString();
351    }
352
353    /** {@hide} */
354    // TODO: find out where toString() is called internally and replace these calls by dump().
355    public String dump() {
356        final CharArrayWriter writer = new CharArrayWriter();
357        dump(new IndentingPrintWriter(writer, "    ", 80));
358        return writer.toString();
359    }
360
361    /** {@hide} */
362    public void dump(IndentingPrintWriter pw) {
363        pw.println("StorageVolume:");
364        pw.increaseIndent();
365        pw.printPair("mId", mId);
366        pw.printPair("mPath", mPath);
367        pw.printPair("mInternalPath", mInternalPath);
368        pw.printPair("mDescription", mDescription);
369        pw.printPair("mPrimary", mPrimary);
370        pw.printPair("mRemovable", mRemovable);
371        pw.printPair("mEmulated", mEmulated);
372        pw.printPair("mAllowMassStorage", mAllowMassStorage);
373        pw.printPair("mMaxFileSize", mMaxFileSize);
374        pw.printPair("mOwner", mOwner);
375        pw.printPair("mFsUuid", mFsUuid);
376        pw.printPair("mState", mState);
377        pw.decreaseIndent();
378    }
379
380    public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
381        @Override
382        public StorageVolume createFromParcel(Parcel in) {
383            return new StorageVolume(in);
384        }
385
386        @Override
387        public StorageVolume[] newArray(int size) {
388            return new StorageVolume[size];
389        }
390    };
391
392    @Override
393    public int describeContents() {
394        return 0;
395    }
396
397    @Override
398    public void writeToParcel(Parcel parcel, int flags) {
399        parcel.writeString(mId);
400        parcel.writeString(mPath.toString());
401        parcel.writeString(mInternalPath.toString());
402        parcel.writeString(mDescription);
403        parcel.writeInt(mPrimary ? 1 : 0);
404        parcel.writeInt(mRemovable ? 1 : 0);
405        parcel.writeInt(mEmulated ? 1 : 0);
406        parcel.writeInt(mAllowMassStorage ? 1 : 0);
407        parcel.writeLong(mMaxFileSize);
408        parcel.writeParcelable(mOwner, flags);
409        parcel.writeString(mFsUuid);
410        parcel.writeString(mState);
411    }
412
413    /** {@hide} */
414    public static final class ScopedAccessProviderContract {
415
416        private ScopedAccessProviderContract() {
417            throw new UnsupportedOperationException("contains constants only");
418        }
419
420        public static final String AUTHORITY = "com.android.documentsui.scopedAccess";
421
422        public static final String TABLE_PACKAGES = "packages";
423        public static final String TABLE_PERMISSIONS = "permissions";
424
425        public static final String COL_PACKAGE = "package_name";
426        public static final String COL_VOLUME_UUID = "volume_uuid";
427        public static final String COL_DIRECTORY = "directory";
428        public static final String COL_GRANTED = "granted";
429
430        public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE };
431        public static final String[] TABLE_PERMISSIONS_COLUMNS =
432                new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED };
433
434        public static final int TABLE_PACKAGES_COL_PACKAGE = 0;
435        public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0;
436        public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1;
437        public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2;
438        public static final int TABLE_PERMISSIONS_COL_GRANTED = 3;
439    }
440}
441