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.Uri; 23 import android.os.Environment; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.os.UserHandle; 27 import android.provider.DocumentsContract; 28 29 import com.android.internal.util.IndentingPrintWriter; 30 import com.android.internal.util.Preconditions; 31 32 import java.io.CharArrayWriter; 33 import 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. 77 public 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} */ StorageVolume(String id, File path, File internalPath, String description, boolean primary, boolean removable, boolean emulated, boolean allowMassStorage, long maxFileSize, UserHandle owner, String fsUuid, String state)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 StorageVolume(Parcel in)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} */ getId()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 */ getPath()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 */ getInternalPath()175 public String getInternalPath() { 176 return mInternalPath.toString(); 177 } 178 179 /** {@hide} */ getPathFile()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 */ getDescription(Context context)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 */ isPrimary()197 public boolean isPrimary() { 198 return mPrimary; 199 } 200 201 /** 202 * Returns true if the volume is removable. 203 * 204 * @return is removable 205 */ isRemovable()206 public boolean isRemovable() { 207 return mRemovable; 208 } 209 210 /** 211 * Returns true if the volume is emulated. 212 * 213 * @return is removable 214 */ isEmulated()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 */ allowMassStorage()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 */ getMaxFileSize()235 public long getMaxFileSize() { 236 return mMaxFileSize; 237 } 238 239 /** {@hide} */ getOwner()240 public UserHandle getOwner() { 241 return mOwner; 242 } 243 244 /** 245 * Gets the volume UUID, if any. 246 */ getUuid()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 */ getFatVolumeId()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} */ getUserLabel()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 */ getState()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 */ createAccessIntent(String directoryName)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 equals(Object obj)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 hashCode()340 public int hashCode() { 341 return mPath.hashCode(); 342 } 343 344 @Override toString()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(). 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} */ dump(IndentingPrintWriter pw)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 describeContents()393 public int describeContents() { 394 return 0; 395 } 396 397 @Override writeToParcel(Parcel parcel, int flags)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 ScopedAccessProviderContract()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