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