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