VolumeInfo.java revision 620b32b316fd4f1bab4eef55ec8802d14a55e7dd
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; 35 36import com.android.internal.util.IndentingPrintWriter; 37import com.android.internal.util.Preconditions; 38 39import java.io.CharArrayWriter; 40import java.io.File; 41import java.util.Comparator; 42import java.util.Objects; 43 44/** 45 * Information about a storage volume that may be mounted. A volume may be a 46 * partition on a physical {@link DiskInfo}, an emulated volume above some other 47 * storage medium, or a standalone container like an ASEC or OBB. 48 * 49 * @hide 50 */ 51public class VolumeInfo implements Parcelable { 52 public static final String ACTION_VOLUME_STATE_CHANGED = 53 "android.os.storage.action.VOLUME_STATE_CHANGED"; 54 public static final String EXTRA_VOLUME_ID = 55 "android.os.storage.extra.VOLUME_ID"; 56 57 /** Stub volume representing internal private storage */ 58 public static final String ID_PRIVATE_INTERNAL = "private"; 59 /** Real volume representing internal emulated storage */ 60 public static final String ID_EMULATED_INTERNAL = "emulated"; 61 62 public static final int TYPE_PUBLIC = 0; 63 public static final int TYPE_PRIVATE = 1; 64 public static final int TYPE_EMULATED = 2; 65 public static final int TYPE_ASEC = 3; 66 public static final int TYPE_OBB = 4; 67 68 public static final int STATE_UNMOUNTED = 0; 69 public static final int STATE_CHECKING = 1; 70 public static final int STATE_MOUNTED = 2; 71 public static final int STATE_MOUNTED_READ_ONLY = 3; 72 public static final int STATE_FORMATTING = 4; 73 public static final int STATE_EJECTING = 5; 74 public static final int STATE_UNMOUNTABLE = 6; 75 public static final int STATE_REMOVED = 7; 76 public static final int STATE_BAD_REMOVAL = 8; 77 78 public static final int MOUNT_FLAG_PRIMARY = 1 << 0; 79 public static final int MOUNT_FLAG_VISIBLE = 1 << 1; 80 81 public static final int USER_FLAG_INITED = 1 << 0; 82 public static final int USER_FLAG_SNOOZED = 1 << 1; 83 84 private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); 85 private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); 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 124 /** vold state */ 125 public final String id; 126 public final int type; 127 public final DiskInfo disk; 128 public int mountFlags = 0; 129 public int mountUserId = -1; 130 public int state = STATE_UNMOUNTED; 131 public String fsType; 132 public String fsUuid; 133 public String fsLabel; 134 public String path; 135 136 /** Framework state */ 137 public final int mtpIndex; 138 public String nickname; 139 public int userFlags = 0; 140 141 public VolumeInfo(String id, int type, DiskInfo disk, int mtpIndex) { 142 this.id = Preconditions.checkNotNull(id); 143 this.type = type; 144 this.disk = disk; 145 this.mtpIndex = mtpIndex; 146 } 147 148 public VolumeInfo(Parcel parcel) { 149 id = parcel.readString(); 150 type = parcel.readInt(); 151 if (parcel.readInt() != 0) { 152 disk = DiskInfo.CREATOR.createFromParcel(parcel); 153 } else { 154 disk = null; 155 } 156 mountFlags = parcel.readInt(); 157 mountUserId = parcel.readInt(); 158 state = parcel.readInt(); 159 fsType = parcel.readString(); 160 fsUuid = parcel.readString(); 161 fsLabel = parcel.readString(); 162 path = parcel.readString(); 163 mtpIndex = parcel.readInt(); 164 nickname = parcel.readString(); 165 userFlags = parcel.readInt(); 166 } 167 168 public static @NonNull String getEnvironmentForState(int state) { 169 final String envState = sStateToEnvironment.get(state); 170 if (envState != null) { 171 return envState; 172 } else { 173 return Environment.MEDIA_UNKNOWN; 174 } 175 } 176 177 public static @Nullable String getBroadcastForEnvironment(String envState) { 178 return sEnvironmentToBroadcast.get(envState); 179 } 180 181 public static @Nullable String getBroadcastForState(int state) { 182 return getBroadcastForEnvironment(getEnvironmentForState(state)); 183 } 184 185 public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() { 186 return sDescriptionComparator; 187 } 188 189 public @NonNull String getId() { 190 return id; 191 } 192 193 public @Nullable DiskInfo getDisk() { 194 return disk; 195 } 196 197 public @Nullable String getDiskId() { 198 return (disk != null) ? disk.id : null; 199 } 200 201 public int getType() { 202 return type; 203 } 204 205 public int getState() { 206 return state; 207 } 208 209 public @Nullable String getFsUuid() { 210 return fsUuid; 211 } 212 213 public @Nullable String getNickname() { 214 return nickname; 215 } 216 217 public int getMountUserId() { 218 return mountUserId; 219 } 220 221 public @Nullable String getDescription() { 222 if (ID_PRIVATE_INTERNAL.equals(id)) { 223 return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); 224 } else if (!TextUtils.isEmpty(nickname)) { 225 return nickname; 226 } else if (!TextUtils.isEmpty(fsLabel)) { 227 return fsLabel; 228 } else { 229 return null; 230 } 231 } 232 233 public boolean isMountedReadable() { 234 return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY; 235 } 236 237 public boolean isMountedWritable() { 238 return state == STATE_MOUNTED; 239 } 240 241 public boolean isPrimary() { 242 return (mountFlags & MOUNT_FLAG_PRIMARY) != 0; 243 } 244 245 public boolean isPrimaryPhysical() { 246 return isPrimary() && (getType() == TYPE_PUBLIC); 247 } 248 249 public boolean isVisible() { 250 return (mountFlags & MOUNT_FLAG_VISIBLE) != 0; 251 } 252 253 public boolean isInited() { 254 return (userFlags & USER_FLAG_INITED) != 0; 255 } 256 257 public boolean isSnoozed() { 258 return (userFlags & USER_FLAG_SNOOZED) != 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 new File(path); 273 } 274 275 public File getPathForUser(int userId) { 276 if (path == null) { 277 return null; 278 } else if (type == TYPE_PUBLIC && userId == this.mountUserId) { 279 return new File(path); 280 } else if (type == TYPE_EMULATED) { 281 return new File(path, Integer.toString(userId)); 282 } else { 283 return null; 284 } 285 } 286 287 /** 288 * Path which is accessible to apps holding 289 * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. 290 */ 291 public File getInternalPathForUser(int userId) { 292 if (type == TYPE_PUBLIC) { 293 // TODO: plumb through cleaner path from vold 294 return new File(path.replace("/storage/", "/mnt/media_rw/")); 295 } else { 296 return getPathForUser(userId); 297 } 298 } 299 300 public StorageVolume buildStorageVolume(Context context, int userId) { 301 final boolean removable; 302 final boolean emulated; 303 final boolean allowMassStorage = false; 304 final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex); 305 final String envState = getEnvironmentForState(state); 306 307 File userPath = getPathForUser(userId); 308 if (userPath == null) { 309 userPath = new File("/dev/null"); 310 } 311 312 String description = getDescription(); 313 if (description == null) { 314 description = context.getString(android.R.string.unknownName); 315 } 316 317 long mtpReserveSize = 0; 318 long maxFileSize = 0; 319 320 if (type == TYPE_EMULATED) { 321 emulated = true; 322 mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath); 323 324 if (ID_EMULATED_INTERNAL.equals(id)) { 325 removable = false; 326 } else { 327 removable = true; 328 } 329 330 } else if (type == TYPE_PUBLIC) { 331 emulated = false; 332 removable = true; 333 334 if ("vfat".equals(fsType)) { 335 maxFileSize = 4294967295L; 336 } 337 338 } else { 339 throw new IllegalStateException("Unexpected volume type " + type); 340 } 341 342 return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable, 343 emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId), 344 fsUuid, envState); 345 } 346 347 // TODO: avoid this layering violation 348 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; 349 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; 350 351 /** 352 * Build an intent to browse the contents of this volume. Only valid for 353 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}. 354 */ 355 public Intent buildBrowseIntent() { 356 final Uri uri; 357 if (type == VolumeInfo.TYPE_PUBLIC) { 358 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); 359 } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(id)) { 360 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, 361 DOCUMENT_ROOT_PRIMARY_EMULATED); 362 } else if (type == VolumeInfo.TYPE_EMULATED) { 363 // TODO: build intent once supported 364 uri = null; 365 } else { 366 throw new IllegalArgumentException(); 367 } 368 369 final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 370 intent.addCategory(Intent.CATEGORY_DEFAULT); 371 intent.setData(uri); 372 return intent; 373 } 374 375 @Override 376 public String toString() { 377 final CharArrayWriter writer = new CharArrayWriter(); 378 dump(new IndentingPrintWriter(writer, " ", 80)); 379 return writer.toString(); 380 } 381 382 public void dump(IndentingPrintWriter pw) { 383 pw.println("VolumeInfo{" + id + "}:"); 384 pw.increaseIndent(); 385 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type)); 386 pw.printPair("diskId", getDiskId()); 387 pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags)); 388 pw.printPair("mountUserId", mountUserId); 389 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state)); 390 pw.println(); 391 pw.printPair("fsType", fsType); 392 pw.printPair("fsUuid", fsUuid); 393 pw.printPair("fsLabel", fsLabel); 394 pw.println(); 395 pw.printPair("path", path); 396 pw.printPair("mtpIndex", mtpIndex); 397 pw.printPair("nickname", nickname); 398 pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags)); 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.writeInt(mtpIndex); 464 parcel.writeString(nickname); 465 parcel.writeInt(userFlags); 466 } 467} 468