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