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