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