• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.os.storage;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.Uri;
23 import android.os.Environment;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.os.UserHandle;
27 import android.provider.DocumentsContract;
28 
29 import com.android.internal.util.IndentingPrintWriter;
30 import com.android.internal.util.Preconditions;
31 
32 import java.io.CharArrayWriter;
33 import 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.
77 public 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} */
StorageVolume(String id, File path, File internalPath, String description, boolean primary, boolean removable, boolean emulated, boolean allowMassStorage, long maxFileSize, UserHandle owner, String fsUuid, String state)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 
StorageVolume(Parcel in)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} */
getId()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      */
getPath()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      */
getInternalPath()175     public String getInternalPath() {
176         return mInternalPath.toString();
177     }
178 
179     /** {@hide} */
getPathFile()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      */
getDescription(Context context)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      */
isPrimary()197     public boolean isPrimary() {
198         return mPrimary;
199     }
200 
201     /**
202      * Returns true if the volume is removable.
203      *
204      * @return is removable
205      */
isRemovable()206     public boolean isRemovable() {
207         return mRemovable;
208     }
209 
210     /**
211      * Returns true if the volume is emulated.
212      *
213      * @return is removable
214      */
isEmulated()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      */
allowMassStorage()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      */
getMaxFileSize()235     public long getMaxFileSize() {
236         return mMaxFileSize;
237     }
238 
239     /** {@hide} */
getOwner()240     public UserHandle getOwner() {
241         return mOwner;
242     }
243 
244     /**
245      * Gets the volume UUID, if any.
246      */
getUuid()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      */
getFatVolumeId()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} */
getUserLabel()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      */
getState()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      */
createAccessIntent(String directoryName)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
equals(Object obj)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
hashCode()340     public int hashCode() {
341         return mPath.hashCode();
342     }
343 
344     @Override
toString()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().
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} */
dump(IndentingPrintWriter pw)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
describeContents()393     public int describeContents() {
394         return 0;
395     }
396 
397     @Override
writeToParcel(Parcel parcel, int flags)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 
ScopedAccessProviderContract()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