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