VolumeInfo.java revision 5fc247338dfc1a817f708163201cdf395cff3303
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 17package android.os.storage; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.content.Context; 22import android.content.Intent; 23import android.content.res.Resources; 24import android.mtp.MtpStorage; 25import android.net.Uri; 26import android.os.Environment; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.UserHandle; 30import android.provider.DocumentsContract; 31import android.text.TextUtils; 32import android.util.ArrayMap; 33import android.util.DebugUtils; 34import android.util.SparseArray; 35import android.util.SparseIntArray; 36 37import com.android.internal.R; 38import com.android.internal.util.IndentingPrintWriter; 39import com.android.internal.util.Preconditions; 40 41import java.io.CharArrayWriter; 42import java.io.File; 43import java.util.Comparator; 44import 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 * 51 * @hide 52 */ 53public class VolumeInfo implements Parcelable { 54 public static final String ACTION_VOLUME_STATE_CHANGED = 55 "android.os.storage.action.VOLUME_STATE_CHANGED"; 56 public static final String EXTRA_VOLUME_ID = 57 "android.os.storage.extra.VOLUME_ID"; 58 59 /** Stub volume representing internal private storage */ 60 public static final String ID_PRIVATE_INTERNAL = "private"; 61 /** Real volume representing internal emulated storage */ 62 public static final String ID_EMULATED_INTERNAL = "emulated"; 63 64 public static final int TYPE_PUBLIC = 0; 65 public static final int TYPE_PRIVATE = 1; 66 public static final int TYPE_EMULATED = 2; 67 public static final int TYPE_ASEC = 3; 68 public static final int TYPE_OBB = 4; 69 70 public static final int STATE_UNMOUNTED = 0; 71 public static final int STATE_CHECKING = 1; 72 public static final int STATE_MOUNTED = 2; 73 public static final int STATE_MOUNTED_READ_ONLY = 3; 74 public static final int STATE_FORMATTING = 4; 75 public static final int STATE_EJECTING = 5; 76 public static final int STATE_UNMOUNTABLE = 6; 77 public static final int STATE_REMOVED = 7; 78 public static final int STATE_BAD_REMOVAL = 8; 79 80 public static final int MOUNT_FLAG_PRIMARY = 1 << 0; 81 public static final int MOUNT_FLAG_VISIBLE = 1 << 1; 82 83 private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); 84 private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); 85 private static SparseIntArray sStateToDescrip = new SparseIntArray(); 86 87 private static final Comparator<VolumeInfo> 88 sDescriptionComparator = new Comparator<VolumeInfo>() { 89 @Override 90 public int compare(VolumeInfo lhs, VolumeInfo rhs) { 91 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) { 92 return -1; 93 } else if (lhs.getDescription() == null) { 94 return 1; 95 } else if (rhs.getDescription() == null) { 96 return -1; 97 } else { 98 return lhs.getDescription().compareTo(rhs.getDescription()); 99 } 100 } 101 }; 102 103 static { 104 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED); 105 sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING); 106 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED); 107 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY); 108 sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED); 109 sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING); 110 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE); 111 sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED); 112 sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL); 113 114 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED); 115 sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING); 116 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED); 117 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED); 118 sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT); 119 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE); 120 sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED); 121 sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL); 122 123 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted); 124 sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking); 125 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted); 126 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro); 127 sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting); 128 sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting); 129 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable); 130 sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed); 131 sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal); 132 } 133 134 /** vold state */ 135 public final String id; 136 public final int type; 137 public final DiskInfo disk; 138 public int mountFlags = 0; 139 public int mountUserId = -1; 140 public int state = STATE_UNMOUNTED; 141 public String fsType; 142 public String fsUuid; 143 public String fsLabel; 144 public String path; 145 public String internalPath; 146 147 /** Framework state */ 148 public final int mtpIndex; 149 150 public VolumeInfo(String id, int type, DiskInfo disk, int mtpIndex) { 151 this.id = Preconditions.checkNotNull(id); 152 this.type = type; 153 this.disk = disk; 154 this.mtpIndex = mtpIndex; 155 } 156 157 public VolumeInfo(Parcel parcel) { 158 id = parcel.readString(); 159 type = parcel.readInt(); 160 if (parcel.readInt() != 0) { 161 disk = DiskInfo.CREATOR.createFromParcel(parcel); 162 } else { 163 disk = null; 164 } 165 mountFlags = parcel.readInt(); 166 mountUserId = parcel.readInt(); 167 state = parcel.readInt(); 168 fsType = parcel.readString(); 169 fsUuid = parcel.readString(); 170 fsLabel = parcel.readString(); 171 path = parcel.readString(); 172 internalPath = parcel.readString(); 173 mtpIndex = parcel.readInt(); 174 } 175 176 public static @NonNull String getEnvironmentForState(int state) { 177 final String envState = sStateToEnvironment.get(state); 178 if (envState != null) { 179 return envState; 180 } else { 181 return Environment.MEDIA_UNKNOWN; 182 } 183 } 184 185 public static @Nullable String getBroadcastForEnvironment(String envState) { 186 return sEnvironmentToBroadcast.get(envState); 187 } 188 189 public static @Nullable String getBroadcastForState(int state) { 190 return getBroadcastForEnvironment(getEnvironmentForState(state)); 191 } 192 193 public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() { 194 return sDescriptionComparator; 195 } 196 197 public @NonNull String getId() { 198 return id; 199 } 200 201 public @Nullable DiskInfo getDisk() { 202 return disk; 203 } 204 205 public @Nullable String getDiskId() { 206 return (disk != null) ? disk.id : null; 207 } 208 209 public int getType() { 210 return type; 211 } 212 213 public int getState() { 214 return state; 215 } 216 217 public int getStateDescription() { 218 return sStateToDescrip.get(state, 0); 219 } 220 221 public @Nullable String getFsUuid() { 222 return fsUuid; 223 } 224 225 public int getMountUserId() { 226 return mountUserId; 227 } 228 229 public @Nullable String getDescription() { 230 if (ID_PRIVATE_INTERNAL.equals(id)) { 231 return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); 232 } else if (!TextUtils.isEmpty(fsLabel)) { 233 return fsLabel; 234 } else { 235 return null; 236 } 237 } 238 239 public boolean isMountedReadable() { 240 return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY; 241 } 242 243 public boolean isMountedWritable() { 244 return state == STATE_MOUNTED; 245 } 246 247 public boolean isPrimary() { 248 return (mountFlags & MOUNT_FLAG_PRIMARY) != 0; 249 } 250 251 public boolean isPrimaryPhysical() { 252 return isPrimary() && (getType() == TYPE_PUBLIC); 253 } 254 255 public boolean isVisible() { 256 return (mountFlags & MOUNT_FLAG_VISIBLE) != 0; 257 } 258 259 public boolean isVisibleToUser(int userId) { 260 if (type == TYPE_PUBLIC && userId == this.mountUserId) { 261 return isVisible(); 262 } else if (type == TYPE_EMULATED) { 263 return isVisible(); 264 } else { 265 return false; 266 } 267 } 268 269 public File getPath() { 270 return (path != null) ? new File(path) : null; 271 } 272 273 public File getInternalPath() { 274 return (internalPath != null) ? new File(internalPath) : null; 275 } 276 277 public File getPathForUser(int userId) { 278 if (path == null) { 279 return null; 280 } else if (type == TYPE_PUBLIC && userId == this.mountUserId) { 281 return new File(path); 282 } else if (type == TYPE_EMULATED) { 283 return new File(path, Integer.toString(userId)); 284 } else { 285 return null; 286 } 287 } 288 289 /** 290 * Path which is accessible to apps holding 291 * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. 292 */ 293 public File getInternalPathForUser(int userId) { 294 if (type == TYPE_PUBLIC) { 295 // TODO: plumb through cleaner path from vold 296 return new File(path.replace("/storage/", "/mnt/media_rw/")); 297 } else { 298 return getPathForUser(userId); 299 } 300 } 301 302 public StorageVolume buildStorageVolume(Context context, int userId) { 303 final boolean removable; 304 final boolean emulated; 305 final boolean allowMassStorage = false; 306 final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex); 307 final String envState = getEnvironmentForState(state); 308 309 File userPath = getPathForUser(userId); 310 if (userPath == null) { 311 userPath = new File("/dev/null"); 312 } 313 314 String description = getDescription(); 315 if (description == null) { 316 description = context.getString(android.R.string.unknownName); 317 } 318 319 long mtpReserveSize = 0; 320 long maxFileSize = 0; 321 322 if (type == TYPE_EMULATED) { 323 emulated = true; 324 mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath); 325 326 if (ID_EMULATED_INTERNAL.equals(id)) { 327 removable = false; 328 } else { 329 removable = true; 330 } 331 332 } else if (type == TYPE_PUBLIC) { 333 emulated = false; 334 removable = true; 335 336 if ("vfat".equals(fsType)) { 337 maxFileSize = 4294967295L; 338 } 339 340 } else { 341 throw new IllegalStateException("Unexpected volume type " + type); 342 } 343 344 return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable, 345 emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId), 346 fsUuid, envState); 347 } 348 349 // TODO: avoid this layering violation 350 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; 351 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; 352 353 /** 354 * Build an intent to browse the contents of this volume. Only valid for 355 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}. 356 */ 357 public Intent buildBrowseIntent() { 358 final Uri uri; 359 if (type == VolumeInfo.TYPE_PUBLIC) { 360 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); 361 } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) { 362 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, 363 DOCUMENT_ROOT_PRIMARY_EMULATED); 364 } else { 365 return null; 366 } 367 368 final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 369 intent.addCategory(Intent.CATEGORY_DEFAULT); 370 intent.setData(uri); 371 return intent; 372 } 373 374 @Override 375 public String toString() { 376 final CharArrayWriter writer = new CharArrayWriter(); 377 dump(new IndentingPrintWriter(writer, " ", 80)); 378 return writer.toString(); 379 } 380 381 public void dump(IndentingPrintWriter pw) { 382 pw.println("VolumeInfo{" + id + "}:"); 383 pw.increaseIndent(); 384 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type)); 385 pw.printPair("diskId", getDiskId()); 386 pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags)); 387 pw.printPair("mountUserId", mountUserId); 388 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state)); 389 pw.println(); 390 pw.printPair("fsType", fsType); 391 pw.printPair("fsUuid", fsUuid); 392 pw.printPair("fsLabel", fsLabel); 393 pw.println(); 394 pw.printPair("path", path); 395 pw.printPair("internalPath", internalPath); 396 pw.printPair("mtpIndex", mtpIndex); 397 pw.decreaseIndent(); 398 pw.println(); 399 } 400 401 @Override 402 public VolumeInfo clone() { 403 final Parcel temp = Parcel.obtain(); 404 try { 405 writeToParcel(temp, 0); 406 temp.setDataPosition(0); 407 return CREATOR.createFromParcel(temp); 408 } finally { 409 temp.recycle(); 410 } 411 } 412 413 @Override 414 public boolean equals(Object o) { 415 if (o instanceof VolumeInfo) { 416 return Objects.equals(id, ((VolumeInfo) o).id); 417 } else { 418 return false; 419 } 420 } 421 422 @Override 423 public int hashCode() { 424 return id.hashCode(); 425 } 426 427 public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() { 428 @Override 429 public VolumeInfo createFromParcel(Parcel in) { 430 return new VolumeInfo(in); 431 } 432 433 @Override 434 public VolumeInfo[] newArray(int size) { 435 return new VolumeInfo[size]; 436 } 437 }; 438 439 @Override 440 public int describeContents() { 441 return 0; 442 } 443 444 @Override 445 public void writeToParcel(Parcel parcel, int flags) { 446 parcel.writeString(id); 447 parcel.writeInt(type); 448 if (disk != null) { 449 parcel.writeInt(1); 450 disk.writeToParcel(parcel, flags); 451 } else { 452 parcel.writeInt(0); 453 } 454 parcel.writeInt(mountFlags); 455 parcel.writeInt(mountUserId); 456 parcel.writeInt(state); 457 parcel.writeString(fsType); 458 parcel.writeString(fsUuid); 459 parcel.writeString(fsLabel); 460 parcel.writeString(path); 461 parcel.writeString(internalPath); 462 parcel.writeInt(mtpIndex); 463 } 464} 465