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