StorageVolume.java revision db892b84e513f174d29f67d293435407a0ac7c6c
1/* 2 * Copyright (C) 2011 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.net.TrafficStats; 24import android.net.Uri; 25import android.os.Environment; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.os.UserHandle; 29import android.provider.DocumentsContract; 30 31import com.android.internal.util.IndentingPrintWriter; 32import com.android.internal.util.Preconditions; 33 34import java.io.CharArrayWriter; 35import java.io.File; 36 37/** 38 * Information about a shared/external storage volume for a specific user. 39 * 40 * <p> 41 * A device always has one (and one only) primary storage volume, but it could have extra volumes, 42 * like SD cards and USB drives. This object represents the logical view of a storage 43 * volume for a specific user: different users might have different views for the same physical 44 * volume (for example, if the volume is a built-in emulated storage). 45 * 46 * <p> 47 * The storage volume is not necessarily mounted, applications should use {@link #getState()} to 48 * verify its state. 49 * 50 * <p> 51 * Applications willing to read or write to this storage volume needs to get a permission from the 52 * user first, which can be achieved in the following ways: 53 * 54 * <ul> 55 * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they 56 * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a 57 * simpler API and narrows the access to the given directory (and its descendants). 58 * <li>To get access to any directory (and its descendants), they can use the Storage Acess 59 * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and 60 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will 61 * select this specific volume. 62 * <li>To get read and write access to the primary storage volume, applications can declare the 63 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and 64 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the 65 * latter including the former. This approach is discouraged, since users may be hesitant to grant 66 * broad access to all files contained on a storage device. 67 * </ul> 68 * 69 * <p>It can be obtained through {@link StorageManager#getVolumeList()} and 70 * {@link StorageManager#getPrimaryVolume()} and also as an extra in some broadcasts 71 * (see {@link #EXTRA_STORAGE_VOLUME}). 72 * 73 * <p> 74 * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external 75 * storage semantics. 76 */ 77// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific 78// user, but is now part of the public API. 79public final class StorageVolume implements Parcelable { 80 81 private final String mId; 82 private final int mStorageId; 83 private final File mPath; 84 private final String mDescription; 85 private final boolean mPrimary; 86 private final boolean mRemovable; 87 private final boolean mEmulated; 88 private final long mMtpReserveSize; 89 private final boolean mAllowMassStorage; 90 private final long mMaxFileSize; 91 private final UserHandle mOwner; 92 private final String mFsUuid; 93 private final String mState; 94 95 /** 96 * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED}, 97 * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING}, 98 * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED}, 99 * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL}, 100 * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that 101 * contains a {@link StorageVolume}. 102 */ 103 // Also sent on ACTION_MEDIA_UNSHARED, which is @hide 104 public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME"; 105 106 /** 107 * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}. 108 * 109 * @hide 110 */ 111 public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME"; 112 113 /** 114 * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}. 115 */ 116 private static final String ACTION_OPEN_EXTERNAL_DIRECTORY = 117 "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY"; 118 119 /** {@hide} */ 120 public static final int STORAGE_ID_INVALID = 0x00000000; 121 /** {@hide} */ 122 public static final int STORAGE_ID_PRIMARY = 0x00010001; 123 124 /** {@hide} */ 125 public StorageVolume(String id, int storageId, File path, String description, boolean primary, 126 boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage, 127 long maxFileSize, UserHandle owner, String fsUuid, String state) { 128 mId = Preconditions.checkNotNull(id); 129 mStorageId = storageId; 130 mPath = Preconditions.checkNotNull(path); 131 mDescription = Preconditions.checkNotNull(description); 132 mPrimary = primary; 133 mRemovable = removable; 134 mEmulated = emulated; 135 mMtpReserveSize = mtpReserveSize; 136 mAllowMassStorage = allowMassStorage; 137 mMaxFileSize = maxFileSize; 138 mOwner = Preconditions.checkNotNull(owner); 139 mFsUuid = fsUuid; 140 mState = Preconditions.checkNotNull(state); 141 } 142 143 private StorageVolume(Parcel in) { 144 mId = in.readString(); 145 mStorageId = in.readInt(); 146 mPath = new File(in.readString()); 147 mDescription = in.readString(); 148 mPrimary = in.readInt() != 0; 149 mRemovable = in.readInt() != 0; 150 mEmulated = in.readInt() != 0; 151 mMtpReserveSize = in.readLong(); 152 mAllowMassStorage = in.readInt() != 0; 153 mMaxFileSize = in.readLong(); 154 mOwner = in.readParcelable(null); 155 mFsUuid = in.readString(); 156 mState = in.readString(); 157 } 158 159 /** {@hide} */ 160 public String getId() { 161 return mId; 162 } 163 164 /** 165 * Returns the mount path for the volume. 166 * 167 * @return the mount path 168 * @hide 169 */ 170 public String getPath() { 171 return mPath.toString(); 172 } 173 174 /** {@hide} */ 175 public File getPathFile() { 176 return mPath; 177 } 178 179 /** 180 * Returns a user-visible description of the volume. 181 * 182 * @return the volume description 183 */ 184 public String getDescription(Context context) { 185 return mDescription; 186 } 187 188 /** 189 * Returns true if the volume is the primary shared/external storage, which is the volume 190 * backed by {@link Environment#getExternalStorageDirectory()}. 191 */ 192 public boolean isPrimary() { 193 return mPrimary; 194 } 195 196 /** 197 * Returns true if the volume is removable. 198 * 199 * @return is removable 200 */ 201 public boolean isRemovable() { 202 return mRemovable; 203 } 204 205 /** 206 * Returns true if the volume is emulated. 207 * 208 * @return is removable 209 */ 210 public boolean isEmulated() { 211 return mEmulated; 212 } 213 214 /** 215 * Returns the MTP storage ID for the volume. 216 * this is also used for the storage_id column in the media provider. 217 * 218 * @return MTP storage ID 219 * @hide 220 */ 221 public int getStorageId() { 222 return mStorageId; 223 } 224 225 /** 226 * Number of megabytes of space to leave unallocated by MTP. 227 * MTP will subtract this value from the free space it reports back 228 * to the host via GetStorageInfo, and will not allow new files to 229 * be added via MTP if there is less than this amount left free in the storage. 230 * If MTP has dedicated storage this value should be zero, but if MTP is 231 * sharing storage with the rest of the system, set this to a positive value 232 * to ensure that MTP activity does not result in the storage being 233 * too close to full. 234 * 235 * @return MTP reserve space 236 * @hide 237 */ 238 public int getMtpReserveSpace() { 239 return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES); 240 } 241 242 /** 243 * Returns true if this volume can be shared via USB mass storage. 244 * 245 * @return whether mass storage is allowed 246 * @hide 247 */ 248 public boolean allowMassStorage() { 249 return mAllowMassStorage; 250 } 251 252 /** 253 * Returns maximum file size for the volume, or zero if it is unbounded. 254 * 255 * @return maximum file size 256 * @hide 257 */ 258 public long getMaxFileSize() { 259 return mMaxFileSize; 260 } 261 262 /** {@hide} */ 263 public UserHandle getOwner() { 264 return mOwner; 265 } 266 267 /** 268 * Gets the volume UUID, if any. 269 */ 270 public @Nullable String getUuid() { 271 return mFsUuid; 272 } 273 274 /** 275 * Parse and return volume UUID as FAT volume ID, or return -1 if unable to 276 * parse or UUID is unknown. 277 * @hide 278 */ 279 public int getFatVolumeId() { 280 if (mFsUuid == null || mFsUuid.length() != 9) { 281 return -1; 282 } 283 try { 284 return (int) Long.parseLong(mFsUuid.replace("-", ""), 16); 285 } catch (NumberFormatException e) { 286 return -1; 287 } 288 } 289 290 /** {@hide} */ 291 public String getUserLabel() { 292 return mDescription; 293 } 294 295 /** 296 * Returns the current state of the volume. 297 * 298 * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED}, 299 * {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING}, 300 * {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED}, 301 * {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED}, 302 * {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}. 303 */ 304 public String getState() { 305 return mState; 306 } 307 308 /** 309 * Builds an intent to give access to a standard storage directory or entire volume after 310 * obtaining the user's approval. 311 * <p> 312 * When invoked, the system will ask the user to grant access to the requested directory (and 313 * its descendants). The result of the request will be returned to the activity through the 314 * {@code onActivityResult} method. 315 * <p> 316 * To gain access to descendants (child, grandchild, etc) documents, use 317 * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or 318 * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI. 319 * 320 * <b>If your application only needs to store internal data, consider using 321 * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs}, 322 * {@link Context#getExternalCacheDirs()}, or 323 * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write. 324 * 325 * <strong>NOTE: </strong>requesting access to the entire volume is not recommended and it will 326 * result in a stronger message displayed to the user, which may cause the user to reject 327 * the request. 328 * 329 * @param directoryName must be one of 330 * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS}, 331 * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS}, 332 * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES}, 333 * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS}, 334 * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}, or 335 * {code null} to request access to the entire volume. 336 * 337 * @see DocumentsContract 338 */ 339 public Intent createAccessIntent(@NonNull String directoryName) { 340 final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY); 341 intent.putExtra(EXTRA_STORAGE_VOLUME, this); 342 intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName); 343 return intent; 344 } 345 346 @Override 347 public boolean equals(Object obj) { 348 if (obj instanceof StorageVolume && mPath != null) { 349 StorageVolume volume = (StorageVolume)obj; 350 return (mPath.equals(volume.mPath)); 351 } 352 return false; 353 } 354 355 @Override 356 public int hashCode() { 357 return mPath.hashCode(); 358 } 359 360 @Override 361 public String toString() { 362 final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription); 363 if (mFsUuid != null) { 364 buffer.append(" (").append(mFsUuid).append(")"); 365 } 366 return buffer.toString(); 367 } 368 369 /** {@hide} */ 370 // TODO(b/26742218): find out where toString() is called internally and replace these calls by 371 // dump(). 372 public String dump() { 373 final CharArrayWriter writer = new CharArrayWriter(); 374 dump(new IndentingPrintWriter(writer, " ", 80)); 375 return writer.toString(); 376 } 377 378 /** {@hide} */ 379 public void dump(IndentingPrintWriter pw) { 380 pw.println("StorageVolume:"); 381 pw.increaseIndent(); 382 pw.printPair("mId", mId); 383 pw.printPair("mStorageId", mStorageId); 384 pw.printPair("mPath", mPath); 385 pw.printPair("mDescription", mDescription); 386 pw.printPair("mPrimary", mPrimary); 387 pw.printPair("mRemovable", mRemovable); 388 pw.printPair("mEmulated", mEmulated); 389 pw.printPair("mMtpReserveSize", mMtpReserveSize); 390 pw.printPair("mAllowMassStorage", mAllowMassStorage); 391 pw.printPair("mMaxFileSize", mMaxFileSize); 392 pw.printPair("mOwner", mOwner); 393 pw.printPair("mFsUuid", mFsUuid); 394 pw.printPair("mState", mState); 395 pw.decreaseIndent(); 396 } 397 398 public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() { 399 @Override 400 public StorageVolume createFromParcel(Parcel in) { 401 return new StorageVolume(in); 402 } 403 404 @Override 405 public StorageVolume[] newArray(int size) { 406 return new StorageVolume[size]; 407 } 408 }; 409 410 @Override 411 public int describeContents() { 412 return 0; 413 } 414 415 @Override 416 public void writeToParcel(Parcel parcel, int flags) { 417 parcel.writeString(mId); 418 parcel.writeInt(mStorageId); 419 parcel.writeString(mPath.toString()); 420 parcel.writeString(mDescription); 421 parcel.writeInt(mPrimary ? 1 : 0); 422 parcel.writeInt(mRemovable ? 1 : 0); 423 parcel.writeInt(mEmulated ? 1 : 0); 424 parcel.writeLong(mMtpReserveSize); 425 parcel.writeInt(mAllowMassStorage ? 1 : 0); 426 parcel.writeLong(mMaxFileSize); 427 parcel.writeParcelable(mOwner, flags); 428 parcel.writeString(mFsUuid); 429 parcel.writeString(mState); 430 } 431} 432