Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2015 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.content.Context;
     22 import android.content.Intent;
     23 import android.content.res.Resources;
     24 import android.net.Uri;
     25 import android.os.Environment;
     26 import android.os.IVold;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.os.UserHandle;
     30 import android.provider.DocumentsContract;
     31 import android.text.TextUtils;
     32 import android.util.ArrayMap;
     33 import android.util.DebugUtils;
     34 import android.util.SparseArray;
     35 import android.util.SparseIntArray;
     36 
     37 import com.android.internal.R;
     38 import com.android.internal.util.IndentingPrintWriter;
     39 import com.android.internal.util.Preconditions;
     40 
     41 import java.io.CharArrayWriter;
     42 import java.io.File;
     43 import java.util.Comparator;
     44 import java.util.Objects;
     45 
     46 /**
     47  * Information about a storage volume that may be mounted. A volume may be a
     48  * partition on a physical {@link DiskInfo}, an emulated volume above some other
     49  * storage medium, or a standalone container like an ASEC or OBB.
     50  * <p>
     51  * Volumes may be mounted with various flags:
     52  * <ul>
     53  * <li>{@link #MOUNT_FLAG_PRIMARY} means the volume provides primary external
     54  * storage, historically found at {@code /sdcard}.
     55  * <li>{@link #MOUNT_FLAG_VISIBLE} means the volume is visible to third-party
     56  * apps for direct filesystem access. The system should send out relevant
     57  * storage broadcasts and index any media on visible volumes. Visible volumes
     58  * are considered a more stable part of the device, which is why we take the
     59  * time to index them. In particular, transient volumes like USB OTG devices
     60  * <em>should not</em> be marked as visible; their contents should be surfaced
     61  * to apps through the Storage Access Framework.
     62  * </ul>
     63  *
     64  * @hide
     65  */
     66 public class VolumeInfo implements Parcelable {
     67     public static final String ACTION_VOLUME_STATE_CHANGED =
     68             "android.os.storage.action.VOLUME_STATE_CHANGED";
     69     public static final String EXTRA_VOLUME_ID =
     70             "android.os.storage.extra.VOLUME_ID";
     71     public static final String EXTRA_VOLUME_STATE =
     72             "android.os.storage.extra.VOLUME_STATE";
     73 
     74     /** Stub volume representing internal private storage */
     75     public static final String ID_PRIVATE_INTERNAL = "private";
     76     /** Real volume representing internal emulated storage */
     77     public static final String ID_EMULATED_INTERNAL = "emulated";
     78 
     79     public static final int TYPE_PUBLIC = IVold.VOLUME_TYPE_PUBLIC;
     80     public static final int TYPE_PRIVATE = IVold.VOLUME_TYPE_PRIVATE;
     81     public static final int TYPE_EMULATED = IVold.VOLUME_TYPE_EMULATED;
     82     public static final int TYPE_ASEC = IVold.VOLUME_TYPE_ASEC;
     83     public static final int TYPE_OBB = IVold.VOLUME_TYPE_OBB;
     84 
     85     public static final int STATE_UNMOUNTED = IVold.VOLUME_STATE_UNMOUNTED;
     86     public static final int STATE_CHECKING = IVold.VOLUME_STATE_CHECKING;
     87     public static final int STATE_MOUNTED = IVold.VOLUME_STATE_MOUNTED;
     88     public static final int STATE_MOUNTED_READ_ONLY = IVold.VOLUME_STATE_MOUNTED_READ_ONLY;
     89     public static final int STATE_FORMATTING = IVold.VOLUME_STATE_FORMATTING;
     90     public static final int STATE_EJECTING = IVold.VOLUME_STATE_EJECTING;
     91     public static final int STATE_UNMOUNTABLE = IVold.VOLUME_STATE_UNMOUNTABLE;
     92     public static final int STATE_REMOVED = IVold.VOLUME_STATE_REMOVED;
     93     public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL;
     94 
     95     public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY;
     96     public static final int MOUNT_FLAG_VISIBLE = IVold.MOUNT_FLAG_VISIBLE;
     97 
     98     private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
     99     private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
    100     private static SparseIntArray sStateToDescrip = new SparseIntArray();
    101 
    102     private static final Comparator<VolumeInfo>
    103             sDescriptionComparator = new Comparator<VolumeInfo>() {
    104         @Override
    105         public int compare(VolumeInfo lhs, VolumeInfo rhs) {
    106             if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) {
    107                 return -1;
    108             } else if (lhs.getDescription() == null) {
    109                 return 1;
    110             } else if (rhs.getDescription() == null) {
    111                 return -1;
    112             } else {
    113                 return lhs.getDescription().compareTo(rhs.getDescription());
    114             }
    115         }
    116     };
    117 
    118     static {
    119         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
    120         sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
    121         sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
    122         sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY);
    123         sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
    124         sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
    125         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
    126         sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
    127         sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL);
    128 
    129         sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
    130         sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
    131         sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
    132         sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED);
    133         sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
    134         sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
    135         sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
    136         sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL);
    137 
    138         sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted);
    139         sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking);
    140         sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted);
    141         sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro);
    142         sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting);
    143         sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting);
    144         sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable);
    145         sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed);
    146         sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal);
    147     }
    148 
    149     /** vold state */
    150     public final String id;
    151     public final int type;
    152     public final DiskInfo disk;
    153     public final String partGuid;
    154     public int mountFlags = 0;
    155     public int mountUserId = -1;
    156     public int state = STATE_UNMOUNTED;
    157     public String fsType;
    158     public String fsUuid;
    159     public String fsLabel;
    160     public String path;
    161     public String internalPath;
    162 
    163     public VolumeInfo(String id, int type, DiskInfo disk, String partGuid) {
    164         this.id = Preconditions.checkNotNull(id);
    165         this.type = type;
    166         this.disk = disk;
    167         this.partGuid = partGuid;
    168     }
    169 
    170     public VolumeInfo(Parcel parcel) {
    171         id = parcel.readString();
    172         type = parcel.readInt();
    173         if (parcel.readInt() != 0) {
    174             disk = DiskInfo.CREATOR.createFromParcel(parcel);
    175         } else {
    176             disk = null;
    177         }
    178         partGuid = parcel.readString();
    179         mountFlags = parcel.readInt();
    180         mountUserId = parcel.readInt();
    181         state = parcel.readInt();
    182         fsType = parcel.readString();
    183         fsUuid = parcel.readString();
    184         fsLabel = parcel.readString();
    185         path = parcel.readString();
    186         internalPath = parcel.readString();
    187     }
    188 
    189     public static @NonNull String getEnvironmentForState(int state) {
    190         final String envState = sStateToEnvironment.get(state);
    191         if (envState != null) {
    192             return envState;
    193         } else {
    194             return Environment.MEDIA_UNKNOWN;
    195         }
    196     }
    197 
    198     public static @Nullable String getBroadcastForEnvironment(String envState) {
    199         return sEnvironmentToBroadcast.get(envState);
    200     }
    201 
    202     public static @Nullable String getBroadcastForState(int state) {
    203         return getBroadcastForEnvironment(getEnvironmentForState(state));
    204     }
    205 
    206     public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() {
    207         return sDescriptionComparator;
    208     }
    209 
    210     public @NonNull String getId() {
    211         return id;
    212     }
    213 
    214     public @Nullable DiskInfo getDisk() {
    215         return disk;
    216     }
    217 
    218     public @Nullable String getDiskId() {
    219         return (disk != null) ? disk.id : null;
    220     }
    221 
    222     public int getType() {
    223         return type;
    224     }
    225 
    226     public int getState() {
    227         return state;
    228     }
    229 
    230     public int getStateDescription() {
    231         return sStateToDescrip.get(state, 0);
    232     }
    233 
    234     public @Nullable String getFsUuid() {
    235         return fsUuid;
    236     }
    237 
    238     public int getMountUserId() {
    239         return mountUserId;
    240     }
    241 
    242     public @Nullable String getDescription() {
    243         if (ID_PRIVATE_INTERNAL.equals(id) || ID_EMULATED_INTERNAL.equals(id)) {
    244             return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
    245         } else if (!TextUtils.isEmpty(fsLabel)) {
    246             return fsLabel;
    247         } else {
    248             return null;
    249         }
    250     }
    251 
    252     public boolean isMountedReadable() {
    253         return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
    254     }
    255 
    256     public boolean isMountedWritable() {
    257         return state == STATE_MOUNTED;
    258     }
    259 
    260     public boolean isPrimary() {
    261         return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
    262     }
    263 
    264     public boolean isPrimaryPhysical() {
    265         return isPrimary() && (getType() == TYPE_PUBLIC);
    266     }
    267 
    268     public boolean isVisible() {
    269         return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
    270     }
    271 
    272     public boolean isVisibleForUser(int userId) {
    273         if (type == TYPE_PUBLIC && mountUserId == userId) {
    274             return isVisible();
    275         } else if (type == TYPE_EMULATED) {
    276             return isVisible();
    277         } else {
    278             return false;
    279         }
    280     }
    281 
    282     public boolean isVisibleForRead(int userId) {
    283         return isVisibleForUser(userId);
    284     }
    285 
    286     public boolean isVisibleForWrite(int userId) {
    287         return isVisibleForUser(userId);
    288     }
    289 
    290     public File getPath() {
    291         return (path != null) ? new File(path) : null;
    292     }
    293 
    294     public File getInternalPath() {
    295         return (internalPath != null) ? new File(internalPath) : null;
    296     }
    297 
    298     public File getPathForUser(int userId) {
    299         if (path == null) {
    300             return null;
    301         } else if (type == TYPE_PUBLIC) {
    302             return new File(path);
    303         } else if (type == TYPE_EMULATED) {
    304             return new File(path, Integer.toString(userId));
    305         } else {
    306             return null;
    307         }
    308     }
    309 
    310     /**
    311      * Path which is accessible to apps holding
    312      * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
    313      */
    314     public File getInternalPathForUser(int userId) {
    315         if (type == TYPE_PUBLIC) {
    316             // TODO: plumb through cleaner path from vold
    317             return new File(path.replace("/storage/", "/mnt/media_rw/"));
    318         } else {
    319             return getPathForUser(userId);
    320         }
    321     }
    322 
    323     public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
    324         final StorageManager storage = context.getSystemService(StorageManager.class);
    325 
    326         final boolean removable;
    327         final boolean emulated;
    328         final boolean allowMassStorage = false;
    329         final String envState = reportUnmounted
    330                 ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state);
    331 
    332         File userPath = getPathForUser(userId);
    333         if (userPath == null) {
    334             userPath = new File("/dev/null");
    335         }
    336         File internalPath = getInternalPathForUser(userId);
    337         if (internalPath == null) {
    338             internalPath = new File("/dev/null");
    339         }
    340 
    341         String description = null;
    342         String derivedFsUuid = fsUuid;
    343         long maxFileSize = 0;
    344 
    345         if (type == TYPE_EMULATED) {
    346             emulated = true;
    347 
    348             final VolumeInfo privateVol = storage.findPrivateForEmulated(this);
    349             if (privateVol != null) {
    350                 description = storage.getBestVolumeDescription(privateVol);
    351                 derivedFsUuid = privateVol.fsUuid;
    352             }
    353 
    354             if (ID_EMULATED_INTERNAL.equals(id)) {
    355                 removable = false;
    356             } else {
    357                 removable = true;
    358             }
    359 
    360         } else if (type == TYPE_PUBLIC) {
    361             emulated = false;
    362             removable = true;
    363 
    364             description = storage.getBestVolumeDescription(this);
    365 
    366             if ("vfat".equals(fsType)) {
    367                 maxFileSize = 4294967295L;
    368             }
    369 
    370         } else {
    371             throw new IllegalStateException("Unexpected volume type " + type);
    372         }
    373 
    374         if (description == null) {
    375             description = context.getString(android.R.string.unknownName);
    376         }
    377 
    378         return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable,
    379                 emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
    380                 derivedFsUuid, envState);
    381     }
    382 
    383     public static int buildStableMtpStorageId(String fsUuid) {
    384         if (TextUtils.isEmpty(fsUuid)) {
    385             return StorageVolume.STORAGE_ID_INVALID;
    386         } else {
    387             int hash = 0;
    388             for (int i = 0; i < fsUuid.length(); ++i) {
    389                 hash = 31 * hash + fsUuid.charAt(i);
    390             }
    391             hash = (hash ^ (hash << 16)) & 0xffff0000;
    392             // Work around values that the spec doesn't allow, or that we've
    393             // reserved for primary
    394             if (hash == 0x00000000) hash = 0x00020000;
    395             if (hash == 0x00010000) hash = 0x00020000;
    396             if (hash == 0xffff0000) hash = 0xfffe0000;
    397             return hash | 0x0001;
    398         }
    399     }
    400 
    401     // TODO: avoid this layering violation
    402     private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
    403     private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
    404 
    405     /**
    406      * Build an intent to browse the contents of this volume. Only valid for
    407      * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
    408      */
    409     public @Nullable Intent buildBrowseIntent() {
    410         return buildBrowseIntentForUser(UserHandle.myUserId());
    411     }
    412 
    413     public @Nullable Intent buildBrowseIntentForUser(int userId) {
    414         final Uri uri;
    415         if (type == VolumeInfo.TYPE_PUBLIC && mountUserId == userId) {
    416             uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
    417         } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) {
    418             uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
    419                     DOCUMENT_ROOT_PRIMARY_EMULATED);
    420         } else {
    421             return null;
    422         }
    423 
    424         final Intent intent = new Intent(Intent.ACTION_VIEW);
    425         intent.addCategory(Intent.CATEGORY_DEFAULT);
    426         intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
    427 
    428         // note that docsui treats this as *force* show advanced. So sending
    429         // false permits advanced to be shown based on user preferences.
    430         intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
    431         return intent;
    432     }
    433 
    434     @Override
    435     public String toString() {
    436         final CharArrayWriter writer = new CharArrayWriter();
    437         dump(new IndentingPrintWriter(writer, "    ", 80));
    438         return writer.toString();
    439     }
    440 
    441     public void dump(IndentingPrintWriter pw) {
    442         pw.println("VolumeInfo{" + id + "}:");
    443         pw.increaseIndent();
    444         pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
    445         pw.printPair("diskId", getDiskId());
    446         pw.printPair("partGuid", partGuid);
    447         pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
    448         pw.printPair("mountUserId", mountUserId);
    449         pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
    450         pw.println();
    451         pw.printPair("fsType", fsType);
    452         pw.printPair("fsUuid", fsUuid);
    453         pw.printPair("fsLabel", fsLabel);
    454         pw.println();
    455         pw.printPair("path", path);
    456         pw.printPair("internalPath", internalPath);
    457         pw.decreaseIndent();
    458         pw.println();
    459     }
    460 
    461     @Override
    462     public VolumeInfo clone() {
    463         final Parcel temp = Parcel.obtain();
    464         try {
    465             writeToParcel(temp, 0);
    466             temp.setDataPosition(0);
    467             return CREATOR.createFromParcel(temp);
    468         } finally {
    469             temp.recycle();
    470         }
    471     }
    472 
    473     @Override
    474     public boolean equals(Object o) {
    475         if (o instanceof VolumeInfo) {
    476             return Objects.equals(id, ((VolumeInfo) o).id);
    477         } else {
    478             return false;
    479         }
    480     }
    481 
    482     @Override
    483     public int hashCode() {
    484         return id.hashCode();
    485     }
    486 
    487     public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
    488         @Override
    489         public VolumeInfo createFromParcel(Parcel in) {
    490             return new VolumeInfo(in);
    491         }
    492 
    493         @Override
    494         public VolumeInfo[] newArray(int size) {
    495             return new VolumeInfo[size];
    496         }
    497     };
    498 
    499     @Override
    500     public int describeContents() {
    501         return 0;
    502     }
    503 
    504     @Override
    505     public void writeToParcel(Parcel parcel, int flags) {
    506         parcel.writeString(id);
    507         parcel.writeInt(type);
    508         if (disk != null) {
    509             parcel.writeInt(1);
    510             disk.writeToParcel(parcel, flags);
    511         } else {
    512             parcel.writeInt(0);
    513         }
    514         parcel.writeString(partGuid);
    515         parcel.writeInt(mountFlags);
    516         parcel.writeInt(mountUserId);
    517         parcel.writeInt(state);
    518         parcel.writeString(fsType);
    519         parcel.writeString(fsUuid);
    520         parcel.writeString(fsLabel);
    521         parcel.writeString(path);
    522         parcel.writeString(internalPath);
    523     }
    524 }
    525