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.TrafficStats;
23 import android.net.Uri;
24 import android.os.Environment;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.os.UserHandle;
28 import android.provider.DocumentsContract;
29 
30 import com.android.internal.util.IndentingPrintWriter;
31 import com.android.internal.util.Preconditions;
32 
33 import java.io.CharArrayWriter;
34 import 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.
78 public 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} */
StorageVolume(String id, int storageId, File path, String description, boolean primary, boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage, long maxFileSize, UserHandle owner, String fsUuid, String state)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 
StorageVolume(Parcel in)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} */
getId()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      */
getPath()169     public String getPath() {
170         return mPath.toString();
171     }
172 
173     /** {@hide} */
getPathFile()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      */
getDescription(Context context)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      */
isPrimary()191     public boolean isPrimary() {
192         return mPrimary;
193     }
194 
195     /**
196      * Returns true if the volume is removable.
197      *
198      * @return is removable
199      */
isRemovable()200     public boolean isRemovable() {
201         return mRemovable;
202     }
203 
204     /**
205      * Returns true if the volume is emulated.
206      *
207      * @return is removable
208      */
isEmulated()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      */
getStorageId()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      */
getMtpReserveSpace()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      */
allowMassStorage()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      */
getMaxFileSize()257     public long getMaxFileSize() {
258         return mMaxFileSize;
259     }
260 
261     /** {@hide} */
getOwner()262     public UserHandle getOwner() {
263         return mOwner;
264     }
265 
266     /**
267      * Gets the volume UUID, if any.
268      */
getUuid()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      */
getFatVolumeId()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} */
getUserLabel()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      */
getState()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      */
createAccessIntent(String directoryName)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
equals(Object obj)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
hashCode()362     public int hashCode() {
363         return mPath.hashCode();
364     }
365 
366     @Override
toString()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(b/26742218): find out where toString() is called internally and replace these calls by
377     // dump().
dump()378     public String dump() {
379         final CharArrayWriter writer = new CharArrayWriter();
380         dump(new IndentingPrintWriter(writer, "    ", 80));
381         return writer.toString();
382     }
383 
384     /** {@hide} */
dump(IndentingPrintWriter pw)385     public void dump(IndentingPrintWriter pw) {
386         pw.println("StorageVolume:");
387         pw.increaseIndent();
388         pw.printPair("mId", mId);
389         pw.printPair("mStorageId", mStorageId);
390         pw.printPair("mPath", mPath);
391         pw.printPair("mDescription", mDescription);
392         pw.printPair("mPrimary", mPrimary);
393         pw.printPair("mRemovable", mRemovable);
394         pw.printPair("mEmulated", mEmulated);
395         pw.printPair("mMtpReserveSize", mMtpReserveSize);
396         pw.printPair("mAllowMassStorage", mAllowMassStorage);
397         pw.printPair("mMaxFileSize", mMaxFileSize);
398         pw.printPair("mOwner", mOwner);
399         pw.printPair("mFsUuid", mFsUuid);
400         pw.printPair("mState", mState);
401         pw.decreaseIndent();
402     }
403 
404     public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
405         @Override
406         public StorageVolume createFromParcel(Parcel in) {
407             return new StorageVolume(in);
408         }
409 
410         @Override
411         public StorageVolume[] newArray(int size) {
412             return new StorageVolume[size];
413         }
414     };
415 
416     @Override
describeContents()417     public int describeContents() {
418         return 0;
419     }
420 
421     @Override
writeToParcel(Parcel parcel, int flags)422     public void writeToParcel(Parcel parcel, int flags) {
423         parcel.writeString(mId);
424         parcel.writeInt(mStorageId);
425         parcel.writeString(mPath.toString());
426         parcel.writeString(mDescription);
427         parcel.writeInt(mPrimary ? 1 : 0);
428         parcel.writeInt(mRemovable ? 1 : 0);
429         parcel.writeInt(mEmulated ? 1 : 0);
430         parcel.writeLong(mMtpReserveSize);
431         parcel.writeInt(mAllowMassStorage ? 1 : 0);
432         parcel.writeLong(mMaxFileSize);
433         parcel.writeParcelable(mOwner, flags);
434         parcel.writeString(mFsUuid);
435         parcel.writeString(mState);
436     }
437 }
438