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