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