Home | History | Annotate | Download | only in storage
      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} */
    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 
    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} */
    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      */
    169     public String getPath() {
    170         return mPath.toString();
    171     }
    172 
    173     /** {@hide} */
    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      */
    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      */
    191     public boolean isPrimary() {
    192         return mPrimary;
    193     }
    194 
    195     /**
    196      * Returns true if the volume is removable.
    197      *
    198      * @return is removable
    199      */
    200     public boolean isRemovable() {
    201         return mRemovable;
    202     }
    203 
    204     /**
    205      * Returns true if the volume is emulated.
    206      *
    207      * @return is removable
    208      */
    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      */
    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      */
    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      */
    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      */
    257     public long getMaxFileSize() {
    258         return mMaxFileSize;
    259     }
    260 
    261     /** {@hide} */
    262     public UserHandle getOwner() {
    263         return mOwner;
    264     }
    265 
    266     /**
    267      * Gets the volume UUID, if any.
    268      */
    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      */
    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} */
    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      */
    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      */
    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
    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
    362     public int hashCode() {
    363         return mPath.hashCode();
    364     }
    365 
    366     @Override
    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: find out where toString() is called internally and replace these calls by dump().
    377     public String dump() {
    378         final CharArrayWriter writer = new CharArrayWriter();
    379         dump(new IndentingPrintWriter(writer, "    ", 80));
    380         return writer.toString();
    381     }
    382 
    383     /** {@hide} */
    384     public void dump(IndentingPrintWriter pw) {
    385         pw.println("StorageVolume:");
    386         pw.increaseIndent();
    387         pw.printPair("mId", mId);
    388         pw.printPair("mStorageId", mStorageId);
    389         pw.printPair("mPath", mPath);
    390         pw.printPair("mDescription", mDescription);
    391         pw.printPair("mPrimary", mPrimary);
    392         pw.printPair("mRemovable", mRemovable);
    393         pw.printPair("mEmulated", mEmulated);
    394         pw.printPair("mMtpReserveSize", mMtpReserveSize);
    395         pw.printPair("mAllowMassStorage", mAllowMassStorage);
    396         pw.printPair("mMaxFileSize", mMaxFileSize);
    397         pw.printPair("mOwner", mOwner);
    398         pw.printPair("mFsUuid", mFsUuid);
    399         pw.printPair("mState", mState);
    400         pw.decreaseIndent();
    401     }
    402 
    403     public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
    404         @Override
    405         public StorageVolume createFromParcel(Parcel in) {
    406             return new StorageVolume(in);
    407         }
    408 
    409         @Override
    410         public StorageVolume[] newArray(int size) {
    411             return new StorageVolume[size];
    412         }
    413     };
    414 
    415     @Override
    416     public int describeContents() {
    417         return 0;
    418     }
    419 
    420     @Override
    421     public void writeToParcel(Parcel parcel, int flags) {
    422         parcel.writeString(mId);
    423         parcel.writeInt(mStorageId);
    424         parcel.writeString(mPath.toString());
    425         parcel.writeString(mDescription);
    426         parcel.writeInt(mPrimary ? 1 : 0);
    427         parcel.writeInt(mRemovable ? 1 : 0);
    428         parcel.writeInt(mEmulated ? 1 : 0);
    429         parcel.writeLong(mMtpReserveSize);
    430         parcel.writeInt(mAllowMassStorage ? 1 : 0);
    431         parcel.writeLong(mMaxFileSize);
    432         parcel.writeParcelable(mOwner, flags);
    433         parcel.writeString(mFsUuid);
    434         parcel.writeString(mState);
    435     }
    436 }
    437