StorageVolume.java revision 71938e18ca4ad77519da70565710ef37e79443f8
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 File mInternalPath; 82 private final String mDescription; 83 private final boolean mPrimary; 84 private final boolean mRemovable; 85 private final boolean mEmulated; 86 private final boolean mAllowMassStorage; 87 private final long mMaxFileSize; 88 private final UserHandle mOwner; 89 private final String mFsUuid; 90 private final String mState; 91 92 /** 93 * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED}, 94 * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING}, 95 * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED}, 96 * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL}, 97 * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that 98 * contains a {@link StorageVolume}. 99 */ 100 // Also sent on ACTION_MEDIA_UNSHARED, which is @hide 101 public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME"; 102 103 /** 104 * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}. 105 * 106 * @hide 107 */ 108 public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME"; 109 110 /** 111 * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}. 112 */ 113 private static final String ACTION_OPEN_EXTERNAL_DIRECTORY = 114 "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY"; 115 116 /** {@hide} */ 117 public static final int STORAGE_ID_INVALID = 0x00000000; 118 /** {@hide} */ 119 public static final int STORAGE_ID_PRIMARY = 0x00010001; 120 121 /** {@hide} */ 122 public StorageVolume(String id, File path, File internalPath, String description, 123 boolean primary, boolean removable, boolean emulated, boolean allowMassStorage, 124 long maxFileSize, UserHandle owner, String fsUuid, String state) { 125 mId = Preconditions.checkNotNull(id); 126 mPath = Preconditions.checkNotNull(path); 127 mInternalPath = Preconditions.checkNotNull(internalPath); 128 mDescription = Preconditions.checkNotNull(description); 129 mPrimary = primary; 130 mRemovable = removable; 131 mEmulated = emulated; 132 mAllowMassStorage = allowMassStorage; 133 mMaxFileSize = maxFileSize; 134 mOwner = Preconditions.checkNotNull(owner); 135 mFsUuid = fsUuid; 136 mState = Preconditions.checkNotNull(state); 137 } 138 139 private StorageVolume(Parcel in) { 140 mId = in.readString(); 141 mPath = new File(in.readString()); 142 mInternalPath = new File(in.readString()); 143 mDescription = in.readString(); 144 mPrimary = in.readInt() != 0; 145 mRemovable = in.readInt() != 0; 146 mEmulated = in.readInt() != 0; 147 mAllowMassStorage = in.readInt() != 0; 148 mMaxFileSize = in.readLong(); 149 mOwner = in.readParcelable(null); 150 mFsUuid = in.readString(); 151 mState = in.readString(); 152 } 153 154 /** {@hide} */ 155 public String getId() { 156 return mId; 157 } 158 159 /** 160 * Returns the mount path for the volume. 161 * 162 * @return the mount path 163 * @hide 164 */ 165 public String getPath() { 166 return mPath.toString(); 167 } 168 169 /** 170 * Returns the path of the underlying filesystem. 171 * 172 * @return the internal path 173 * @hide 174 */ 175 public String getInternalPath() { 176 return mInternalPath.toString(); 177 } 178 179 /** {@hide} */ 180 public File getPathFile() { 181 return mPath; 182 } 183 184 /** 185 * Returns a user-visible description of the volume. 186 * 187 * @return the volume description 188 */ 189 public String getDescription(Context context) { 190 return mDescription; 191 } 192 193 /** 194 * Returns true if the volume is the primary shared/external storage, which is the volume 195 * backed by {@link Environment#getExternalStorageDirectory()}. 196 */ 197 public boolean isPrimary() { 198 return mPrimary; 199 } 200 201 /** 202 * Returns true if the volume is removable. 203 * 204 * @return is removable 205 */ 206 public boolean isRemovable() { 207 return mRemovable; 208 } 209 210 /** 211 * Returns true if the volume is emulated. 212 * 213 * @return is removable 214 */ 215 public boolean isEmulated() { 216 return mEmulated; 217 } 218 219 /** 220 * Returns true if this volume can be shared via USB mass storage. 221 * 222 * @return whether mass storage is allowed 223 * @hide 224 */ 225 public boolean allowMassStorage() { 226 return mAllowMassStorage; 227 } 228 229 /** 230 * Returns maximum file size for the volume, or zero if it is unbounded. 231 * 232 * @return maximum file size 233 * @hide 234 */ 235 public long getMaxFileSize() { 236 return mMaxFileSize; 237 } 238 239 /** {@hide} */ 240 public UserHandle getOwner() { 241 return mOwner; 242 } 243 244 /** 245 * Gets the volume UUID, if any. 246 */ 247 public @Nullable String getUuid() { 248 return mFsUuid; 249 } 250 251 /** 252 * Parse and return volume UUID as FAT volume ID, or return -1 if unable to 253 * parse or UUID is unknown. 254 * @hide 255 */ 256 public int getFatVolumeId() { 257 if (mFsUuid == null || mFsUuid.length() != 9) { 258 return -1; 259 } 260 try { 261 return (int) Long.parseLong(mFsUuid.replace("-", ""), 16); 262 } catch (NumberFormatException e) { 263 return -1; 264 } 265 } 266 267 /** {@hide} */ 268 public String getUserLabel() { 269 return mDescription; 270 } 271 272 /** 273 * Returns the current state of the volume. 274 * 275 * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED}, 276 * {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING}, 277 * {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED}, 278 * {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED}, 279 * {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}. 280 */ 281 public String getState() { 282 return mState; 283 } 284 285 /** 286 * Builds an intent to give access to a standard storage directory or entire volume after 287 * obtaining the user's approval. 288 * <p> 289 * When invoked, the system will ask the user to grant access to the requested directory (and 290 * its descendants). The result of the request will be returned to the activity through the 291 * {@code onActivityResult} method. 292 * <p> 293 * To gain access to descendants (child, grandchild, etc) documents, use 294 * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or 295 * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI. 296 * <p> 297 * If your application only needs to store internal data, consider using 298 * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs}, 299 * {@link Context#getExternalCacheDirs()}, or {@link Context#getExternalMediaDirs()}, which 300 * require no permissions to read or write. 301 * <p> 302 * Access to the entire volume is only available for non-primary volumes (for the primary 303 * volume, apps can use the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and 304 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions) and should be used 305 * with caution, since users are more likely to deny access when asked for entire volume access 306 * rather than specific directories. 307 * 308 * @param directoryName must be one of {@link Environment#DIRECTORY_MUSIC}, 309 * {@link Environment#DIRECTORY_PODCASTS}, {@link Environment#DIRECTORY_RINGTONES}, 310 * {@link Environment#DIRECTORY_ALARMS}, {@link Environment#DIRECTORY_NOTIFICATIONS}, 311 * {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES}, 312 * {@link Environment#DIRECTORY_DOWNLOADS}, {@link Environment#DIRECTORY_DCIM}, or 313 * {@link Environment#DIRECTORY_DOCUMENTS}, or {@code null} to request access to the 314 * entire volume. 315 * @return intent to request access, or {@code null} if the requested directory is invalid for 316 * that volume. 317 * @see DocumentsContract 318 */ 319 public @Nullable Intent createAccessIntent(String directoryName) { 320 if ((isPrimary() && directoryName == null) || 321 (directoryName != null && !Environment.isStandardDirectory(directoryName))) { 322 return null; 323 } 324 final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY); 325 intent.putExtra(EXTRA_STORAGE_VOLUME, this); 326 intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName); 327 return intent; 328 } 329 330 @Override 331 public boolean equals(Object obj) { 332 if (obj instanceof StorageVolume && mPath != null) { 333 StorageVolume volume = (StorageVolume)obj; 334 return (mPath.equals(volume.mPath)); 335 } 336 return false; 337 } 338 339 @Override 340 public int hashCode() { 341 return mPath.hashCode(); 342 } 343 344 @Override 345 public String toString() { 346 final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription); 347 if (mFsUuid != null) { 348 buffer.append(" (").append(mFsUuid).append(")"); 349 } 350 return buffer.toString(); 351 } 352 353 /** {@hide} */ 354 // TODO: find out where toString() is called internally and replace these calls by dump(). 355 public String dump() { 356 final CharArrayWriter writer = new CharArrayWriter(); 357 dump(new IndentingPrintWriter(writer, " ", 80)); 358 return writer.toString(); 359 } 360 361 /** {@hide} */ 362 public void dump(IndentingPrintWriter pw) { 363 pw.println("StorageVolume:"); 364 pw.increaseIndent(); 365 pw.printPair("mId", mId); 366 pw.printPair("mPath", mPath); 367 pw.printPair("mInternalPath", mInternalPath); 368 pw.printPair("mDescription", mDescription); 369 pw.printPair("mPrimary", mPrimary); 370 pw.printPair("mRemovable", mRemovable); 371 pw.printPair("mEmulated", mEmulated); 372 pw.printPair("mAllowMassStorage", mAllowMassStorage); 373 pw.printPair("mMaxFileSize", mMaxFileSize); 374 pw.printPair("mOwner", mOwner); 375 pw.printPair("mFsUuid", mFsUuid); 376 pw.printPair("mState", mState); 377 pw.decreaseIndent(); 378 } 379 380 public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() { 381 @Override 382 public StorageVolume createFromParcel(Parcel in) { 383 return new StorageVolume(in); 384 } 385 386 @Override 387 public StorageVolume[] newArray(int size) { 388 return new StorageVolume[size]; 389 } 390 }; 391 392 @Override 393 public int describeContents() { 394 return 0; 395 } 396 397 @Override 398 public void writeToParcel(Parcel parcel, int flags) { 399 parcel.writeString(mId); 400 parcel.writeString(mPath.toString()); 401 parcel.writeString(mInternalPath.toString()); 402 parcel.writeString(mDescription); 403 parcel.writeInt(mPrimary ? 1 : 0); 404 parcel.writeInt(mRemovable ? 1 : 0); 405 parcel.writeInt(mEmulated ? 1 : 0); 406 parcel.writeInt(mAllowMassStorage ? 1 : 0); 407 parcel.writeLong(mMaxFileSize); 408 parcel.writeParcelable(mOwner, flags); 409 parcel.writeString(mFsUuid); 410 parcel.writeString(mState); 411 } 412 413 /** {@hide} */ 414 public static final class ScopedAccessProviderContract { 415 416 private ScopedAccessProviderContract() { 417 throw new UnsupportedOperationException("contains constants only"); 418 } 419 420 public static final String AUTHORITY = "com.android.documentsui.scopedAccess"; 421 422 public static final String TABLE_PACKAGES = "packages"; 423 public static final String TABLE_PERMISSIONS = "permissions"; 424 425 public static final String COL_PACKAGE = "package_name"; 426 public static final String COL_VOLUME_UUID = "volume_uuid"; 427 public static final String COL_DIRECTORY = "directory"; 428 public static final String COL_GRANTED = "granted"; 429 430 public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE }; 431 public static final String[] TABLE_PERMISSIONS_COLUMNS = 432 new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED }; 433 434 public static final int TABLE_PACKAGES_COL_PACKAGE = 0; 435 public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0; 436 public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1; 437 public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2; 438 public static final int TABLE_PERMISSIONS_COL_GRANTED = 3; 439 } 440} 441