VolumeInfo.java revision 7a788a865e72da4205b5cf4e0a6f08ccb6f4bdbd
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 /** Framework state */ 151 public final int mtpIndex; 152 153 public VolumeInfo(String id, int type, DiskInfo disk, String partGuid, int mtpIndex) { 154 this.id = Preconditions.checkNotNull(id); 155 this.type = type; 156 this.disk = disk; 157 this.partGuid = partGuid; 158 this.mtpIndex = mtpIndex; 159 } 160 161 public VolumeInfo(Parcel parcel) { 162 id = parcel.readString(); 163 type = parcel.readInt(); 164 if (parcel.readInt() != 0) { 165 disk = DiskInfo.CREATOR.createFromParcel(parcel); 166 } else { 167 disk = null; 168 } 169 partGuid = parcel.readString(); 170 mountFlags = parcel.readInt(); 171 mountUserId = parcel.readInt(); 172 state = parcel.readInt(); 173 fsType = parcel.readString(); 174 fsUuid = parcel.readString(); 175 fsLabel = parcel.readString(); 176 path = parcel.readString(); 177 internalPath = parcel.readString(); 178 mtpIndex = parcel.readInt(); 179 } 180 181 public static @NonNull String getEnvironmentForState(int state) { 182 final String envState = sStateToEnvironment.get(state); 183 if (envState != null) { 184 return envState; 185 } else { 186 return Environment.MEDIA_UNKNOWN; 187 } 188 } 189 190 public static @Nullable String getBroadcastForEnvironment(String envState) { 191 return sEnvironmentToBroadcast.get(envState); 192 } 193 194 public static @Nullable String getBroadcastForState(int state) { 195 return getBroadcastForEnvironment(getEnvironmentForState(state)); 196 } 197 198 public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() { 199 return sDescriptionComparator; 200 } 201 202 public @NonNull String getId() { 203 return id; 204 } 205 206 public @Nullable DiskInfo getDisk() { 207 return disk; 208 } 209 210 public @Nullable String getDiskId() { 211 return (disk != null) ? disk.id : null; 212 } 213 214 public int getType() { 215 return type; 216 } 217 218 public int getState() { 219 return state; 220 } 221 222 public int getStateDescription() { 223 return sStateToDescrip.get(state, 0); 224 } 225 226 public @Nullable String getFsUuid() { 227 return fsUuid; 228 } 229 230 public int getMountUserId() { 231 return mountUserId; 232 } 233 234 public @Nullable String getDescription() { 235 if (ID_PRIVATE_INTERNAL.equals(id) || ID_EMULATED_INTERNAL.equals(id)) { 236 return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); 237 } else if (!TextUtils.isEmpty(fsLabel)) { 238 return fsLabel; 239 } else { 240 return null; 241 } 242 } 243 244 public boolean isMountedReadable() { 245 return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY; 246 } 247 248 public boolean isMountedWritable() { 249 return state == STATE_MOUNTED; 250 } 251 252 public boolean isPrimary() { 253 return (mountFlags & MOUNT_FLAG_PRIMARY) != 0; 254 } 255 256 public boolean isPrimaryPhysical() { 257 return isPrimary() && (getType() == TYPE_PUBLIC); 258 } 259 260 public boolean isVisible() { 261 return (mountFlags & MOUNT_FLAG_VISIBLE) != 0; 262 } 263 264 public boolean isVisibleToUser(int userId) { 265 if (type == TYPE_PUBLIC && userId == this.mountUserId) { 266 return isVisible(); 267 } else if (type == TYPE_EMULATED) { 268 return isVisible(); 269 } else { 270 return false; 271 } 272 } 273 274 public File getPath() { 275 return (path != null) ? new File(path) : null; 276 } 277 278 public File getInternalPath() { 279 return (internalPath != null) ? new File(internalPath) : null; 280 } 281 282 public File getPathForUser(int userId) { 283 if (path == null) { 284 return null; 285 } else if (type == TYPE_PUBLIC && userId == this.mountUserId) { 286 return new File(path); 287 } else if (type == TYPE_EMULATED) { 288 return new File(path, Integer.toString(userId)); 289 } else { 290 return null; 291 } 292 } 293 294 /** 295 * Path which is accessible to apps holding 296 * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. 297 */ 298 public File getInternalPathForUser(int userId) { 299 if (type == TYPE_PUBLIC) { 300 // TODO: plumb through cleaner path from vold 301 return new File(path.replace("/storage/", "/mnt/media_rw/")); 302 } else { 303 return getPathForUser(userId); 304 } 305 } 306 307 public StorageVolume buildStorageVolume(Context context, int userId) { 308 final boolean removable; 309 final boolean emulated; 310 final boolean allowMassStorage = false; 311 final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex); 312 final String envState = getEnvironmentForState(state); 313 314 File userPath = getPathForUser(userId); 315 if (userPath == null) { 316 userPath = new File("/dev/null"); 317 } 318 319 String description = getDescription(); 320 if (description == null) { 321 description = getFsUuid(); 322 } 323 if (description == null) { 324 description = context.getString(android.R.string.unknownName); 325 } 326 327 long mtpReserveSize = 0; 328 long maxFileSize = 0; 329 330 if (type == TYPE_EMULATED) { 331 emulated = true; 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 ("vfat".equals(fsType)) { 345 maxFileSize = 4294967295L; 346 } 347 348 } else { 349 throw new IllegalStateException("Unexpected volume type " + type); 350 } 351 352 return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable, 353 emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId), 354 fsUuid, envState); 355 } 356 357 // TODO: avoid this layering violation 358 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; 359 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; 360 361 /** 362 * Build an intent to browse the contents of this volume. Only valid for 363 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}. 364 */ 365 public Intent buildBrowseIntent() { 366 final Uri uri; 367 if (type == VolumeInfo.TYPE_PUBLIC) { 368 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); 369 } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) { 370 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, 371 DOCUMENT_ROOT_PRIMARY_EMULATED); 372 } else { 373 return null; 374 } 375 376 final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 377 intent.addCategory(Intent.CATEGORY_DEFAULT); 378 intent.setData(uri); 379 return intent; 380 } 381 382 @Override 383 public String toString() { 384 final CharArrayWriter writer = new CharArrayWriter(); 385 dump(new IndentingPrintWriter(writer, " ", 80)); 386 return writer.toString(); 387 } 388 389 public void dump(IndentingPrintWriter pw) { 390 pw.println("VolumeInfo{" + id + "}:"); 391 pw.increaseIndent(); 392 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type)); 393 pw.printPair("diskId", getDiskId()); 394 pw.printPair("partGuid", partGuid); 395 pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags)); 396 pw.printPair("mountUserId", mountUserId); 397 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state)); 398 pw.println(); 399 pw.printPair("fsType", fsType); 400 pw.printPair("fsUuid", fsUuid); 401 pw.printPair("fsLabel", fsLabel); 402 pw.println(); 403 pw.printPair("path", path); 404 pw.printPair("internalPath", internalPath); 405 pw.printPair("mtpIndex", mtpIndex); 406 pw.decreaseIndent(); 407 pw.println(); 408 } 409 410 @Override 411 public VolumeInfo clone() { 412 final Parcel temp = Parcel.obtain(); 413 try { 414 writeToParcel(temp, 0); 415 temp.setDataPosition(0); 416 return CREATOR.createFromParcel(temp); 417 } finally { 418 temp.recycle(); 419 } 420 } 421 422 @Override 423 public boolean equals(Object o) { 424 if (o instanceof VolumeInfo) { 425 return Objects.equals(id, ((VolumeInfo) o).id); 426 } else { 427 return false; 428 } 429 } 430 431 @Override 432 public int hashCode() { 433 return id.hashCode(); 434 } 435 436 public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() { 437 @Override 438 public VolumeInfo createFromParcel(Parcel in) { 439 return new VolumeInfo(in); 440 } 441 442 @Override 443 public VolumeInfo[] newArray(int size) { 444 return new VolumeInfo[size]; 445 } 446 }; 447 448 @Override 449 public int describeContents() { 450 return 0; 451 } 452 453 @Override 454 public void writeToParcel(Parcel parcel, int flags) { 455 parcel.writeString(id); 456 parcel.writeInt(type); 457 if (disk != null) { 458 parcel.writeInt(1); 459 disk.writeToParcel(parcel, flags); 460 } else { 461 parcel.writeInt(0); 462 } 463 parcel.writeString(partGuid); 464 parcel.writeInt(mountFlags); 465 parcel.writeInt(mountUserId); 466 parcel.writeInt(state); 467 parcel.writeString(fsType); 468 parcel.writeString(fsUuid); 469 parcel.writeString(fsLabel); 470 parcel.writeString(path); 471 parcel.writeString(internalPath); 472 parcel.writeInt(mtpIndex); 473 } 474} 475