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} */ 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 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} */ 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 */ 169 public String getPath() { 170 return mPath.toString(); 171 } 172 173 /** {@hide} */ 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 */ 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 */ 191 public boolean isPrimary() { 192 return mPrimary; 193 } 194 195 /** 196 * Returns true if the volume is removable. 197 * 198 * @return is removable 199 */ 200 public boolean isRemovable() { 201 return mRemovable; 202 } 203 204 /** 205 * Returns true if the volume is emulated. 206 * 207 * @return is removable 208 */ 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 */ 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 */ 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 */ 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 */ 257 public long getMaxFileSize() { 258 return mMaxFileSize; 259 } 260 261 /** {@hide} */ 262 public UserHandle getOwner() { 263 return mOwner; 264 } 265 266 /** 267 * Gets the volume UUID, if any. 268 */ 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 */ 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} */ 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 */ 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 */ 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 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 362 public int hashCode() { 363 return mPath.hashCode(); 364 } 365 366 @Override 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(). 378 public String dump() { 379 final CharArrayWriter writer = new CharArrayWriter(); 380 dump(new IndentingPrintWriter(writer, " ", 80)); 381 return writer.toString(); 382 } 383 384 /** {@hide} */ 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 417 public int describeContents() { 418 return 0; 419 } 420 421 @Override 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