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.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} */
    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