VolumeInfo.java revision 5af1835d678031d4a6615edc96ba58c82304b31d
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 public static final String EXTRA_VOLUME_STATE = 59 "android.os.storage.extra.VOLUME_STATE"; 60 61 /** Stub volume representing internal private storage */ 62 public static final String ID_PRIVATE_INTERNAL = "private"; 63 /** Real volume representing internal emulated storage */ 64 public static final String ID_EMULATED_INTERNAL = "emulated"; 65 66 public static final int TYPE_PUBLIC = 0; 67 public static final int TYPE_PRIVATE = 1; 68 public static final int TYPE_EMULATED = 2; 69 public static final int TYPE_ASEC = 3; 70 public static final int TYPE_OBB = 4; 71 72 public static final int STATE_UNMOUNTED = 0; 73 public static final int STATE_CHECKING = 1; 74 public static final int STATE_MOUNTED = 2; 75 public static final int STATE_MOUNTED_READ_ONLY = 3; 76 public static final int STATE_FORMATTING = 4; 77 public static final int STATE_EJECTING = 5; 78 public static final int STATE_UNMOUNTABLE = 6; 79 public static final int STATE_REMOVED = 7; 80 public static final int STATE_BAD_REMOVAL = 8; 81 82 public static final int MOUNT_FLAG_PRIMARY = 1 << 0; 83 public static final int MOUNT_FLAG_VISIBLE = 1 << 1; 84 85 private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); 86 private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); 87 private static SparseIntArray sStateToDescrip = new SparseIntArray(); 88 89 private static final Comparator<VolumeInfo> 90 sDescriptionComparator = new Comparator<VolumeInfo>() { 91 @Override 92 public int compare(VolumeInfo lhs, VolumeInfo rhs) { 93 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) { 94 return -1; 95 } else if (lhs.getDescription() == null) { 96 return 1; 97 } else if (rhs.getDescription() == null) { 98 return -1; 99 } else { 100 return lhs.getDescription().compareTo(rhs.getDescription()); 101 } 102 } 103 }; 104 105 static { 106 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED); 107 sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING); 108 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED); 109 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY); 110 sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED); 111 sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING); 112 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE); 113 sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED); 114 sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL); 115 116 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED); 117 sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING); 118 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED); 119 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED); 120 sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT); 121 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE); 122 sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED); 123 sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL); 124 125 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted); 126 sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking); 127 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted); 128 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro); 129 sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting); 130 sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting); 131 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable); 132 sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed); 133 sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal); 134 } 135 136 /** vold state */ 137 public final String id; 138 public final int type; 139 public final DiskInfo disk; 140 public final String partGuid; 141 public int mountFlags = 0; 142 public int mountUserId = -1; 143 public int state = STATE_UNMOUNTED; 144 public String fsType; 145 public String fsUuid; 146 public String fsLabel; 147 public String path; 148 public String internalPath; 149 150 public VolumeInfo(String id, int type, DiskInfo disk, String partGuid) { 151 this.id = Preconditions.checkNotNull(id); 152 this.type = type; 153 this.disk = disk; 154 this.partGuid = partGuid; 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 partGuid = parcel.readString(); 166 mountFlags = parcel.readInt(); 167 mountUserId = parcel.readInt(); 168 state = parcel.readInt(); 169 fsType = parcel.readString(); 170 fsUuid = parcel.readString(); 171 fsLabel = parcel.readString(); 172 path = parcel.readString(); 173 internalPath = parcel.readString(); 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) || ID_EMULATED_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 String envState = getEnvironmentForState(state); 307 308 File userPath = getPathForUser(userId); 309 if (userPath == null) { 310 userPath = new File("/dev/null"); 311 } 312 313 String description = getDescription(); 314 if (description == null) { 315 description = getFsUuid(); 316 } 317 if (description == null) { 318 description = context.getString(android.R.string.unknownName); 319 } 320 321 long mtpReserveSize = 0; 322 long maxFileSize = 0; 323 int mtpStorageId = StorageVolume.STORAGE_ID_INVALID; 324 325 if (type == TYPE_EMULATED) { 326 emulated = true; 327 328 if (isPrimary()) { 329 mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY; 330 } 331 332 mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath); 333 334 if (ID_EMULATED_INTERNAL.equals(id)) { 335 removable = false; 336 } else { 337 removable = true; 338 } 339 340 } else if (type == TYPE_PUBLIC) { 341 emulated = false; 342 removable = true; 343 344 if (isPrimary()) { 345 mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY; 346 } else { 347 // Since MediaProvider currently persists this value, we need a 348 // value that is stable over time. 349 mtpStorageId = buildStableMtpStorageId(fsUuid); 350 } 351 352 if ("vfat".equals(fsType)) { 353 maxFileSize = 4294967295L; 354 } 355 356 } else { 357 throw new IllegalStateException("Unexpected volume type " + type); 358 } 359 360 return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable, 361 emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId), 362 fsUuid, envState); 363 } 364 365 public static int buildStableMtpStorageId(String fsUuid) { 366 if (TextUtils.isEmpty(fsUuid)) { 367 return StorageVolume.STORAGE_ID_INVALID; 368 } else { 369 int hash = 0; 370 for (int i = 0; i < fsUuid.length(); ++i) { 371 hash = 31 * hash + fsUuid.charAt(i); 372 } 373 hash = (hash ^ (hash << 16)) & 0xffff0000; 374 // Work around values that the spec doesn't allow, or that we've 375 // reserved for primary 376 if (hash == 0x00000000) hash = 0x00020000; 377 if (hash == 0x00010000) hash = 0x00020000; 378 if (hash == 0xffff0000) hash = 0xfffe0000; 379 return hash | 0x0001; 380 } 381 } 382 383 // TODO: avoid this layering violation 384 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; 385 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; 386 387 /** 388 * Build an intent to browse the contents of this volume. Only valid for 389 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}. 390 */ 391 public Intent buildBrowseIntent() { 392 final Uri uri; 393 if (type == VolumeInfo.TYPE_PUBLIC) { 394 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); 395 } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) { 396 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, 397 DOCUMENT_ROOT_PRIMARY_EMULATED); 398 } else { 399 return null; 400 } 401 402 final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 403 intent.addCategory(Intent.CATEGORY_DEFAULT); 404 intent.setData(uri); 405 return intent; 406 } 407 408 @Override 409 public String toString() { 410 final CharArrayWriter writer = new CharArrayWriter(); 411 dump(new IndentingPrintWriter(writer, " ", 80)); 412 return writer.toString(); 413 } 414 415 public void dump(IndentingPrintWriter pw) { 416 pw.println("VolumeInfo{" + id + "}:"); 417 pw.increaseIndent(); 418 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type)); 419 pw.printPair("diskId", getDiskId()); 420 pw.printPair("partGuid", partGuid); 421 pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags)); 422 pw.printPair("mountUserId", mountUserId); 423 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state)); 424 pw.println(); 425 pw.printPair("fsType", fsType); 426 pw.printPair("fsUuid", fsUuid); 427 pw.printPair("fsLabel", fsLabel); 428 pw.println(); 429 pw.printPair("path", path); 430 pw.printPair("internalPath", internalPath); 431 pw.decreaseIndent(); 432 pw.println(); 433 } 434 435 @Override 436 public VolumeInfo clone() { 437 final Parcel temp = Parcel.obtain(); 438 try { 439 writeToParcel(temp, 0); 440 temp.setDataPosition(0); 441 return CREATOR.createFromParcel(temp); 442 } finally { 443 temp.recycle(); 444 } 445 } 446 447 @Override 448 public boolean equals(Object o) { 449 if (o instanceof VolumeInfo) { 450 return Objects.equals(id, ((VolumeInfo) o).id); 451 } else { 452 return false; 453 } 454 } 455 456 @Override 457 public int hashCode() { 458 return id.hashCode(); 459 } 460 461 public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() { 462 @Override 463 public VolumeInfo createFromParcel(Parcel in) { 464 return new VolumeInfo(in); 465 } 466 467 @Override 468 public VolumeInfo[] newArray(int size) { 469 return new VolumeInfo[size]; 470 } 471 }; 472 473 @Override 474 public int describeContents() { 475 return 0; 476 } 477 478 @Override 479 public void writeToParcel(Parcel parcel, int flags) { 480 parcel.writeString(id); 481 parcel.writeInt(type); 482 if (disk != null) { 483 parcel.writeInt(1); 484 disk.writeToParcel(parcel, flags); 485 } else { 486 parcel.writeInt(0); 487 } 488 parcel.writeString(partGuid); 489 parcel.writeInt(mountFlags); 490 parcel.writeInt(mountUserId); 491 parcel.writeInt(state); 492 parcel.writeString(fsType); 493 parcel.writeString(fsUuid); 494 parcel.writeString(fsLabel); 495 parcel.writeString(path); 496 parcel.writeString(internalPath); 497 } 498} 499