Merge "Initial implementation of StorageManager.getVolumesList()." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 2eba30f..5d28d2c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8534,7 +8534,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29365,11 +29364,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
diff --git a/api/removed.txt b/api/removed.txt
index 0bf6594..946f75f 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -15,6 +15,14 @@
 
 }
 
+package android.content {
+
+  public class Intent implements java.lang.Cloneable android.os.Parcelable {
+    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
+  }
+
+}
+
 package android.content.pm {
 
   public class PackageInfo implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index d8a9ca3..a2943ff 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8840,7 +8840,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -31719,11 +31718,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 27de913..ca3c85a 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -6,6 +6,14 @@
 
 }
 
+package android.content {
+
+  public class Intent implements java.lang.Cloneable android.os.Parcelable {
+    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
+  }
+
+}
+
 package android.content.pm {
 
   public class PackageInfo implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 2fff729..d42c18c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8539,7 +8539,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29375,11 +29374,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 0bf6594..946f75f 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -15,6 +15,14 @@
 
 }
 
+package android.content {
+
+  public class Intent implements java.lang.Cloneable android.os.Parcelable {
+    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
+  }
+
+}
+
 package android.content.pm {
 
   public class PackageInfo implements android.os.Parcelable {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b476a25..4e78b8a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3213,6 +3213,12 @@
      * Output: The URI representing the requested directory tree.
      *
      * @see DocumentsContract
+     *
+     * {@removed}
+     *
+     * Will be removed / hidden before N is published; apps should use
+     * {@link android.os.storage.StorageManager#getVolumeList()} and
+     * {@link android.os.storage.StorageVolume#createAccessIntent(String)} instead.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 70f9cc5..1085b1e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -339,7 +339,7 @@
      * <p>
      * Writing to this path requires the
      * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission,
-     * and starting in read access requires the
+     * and starting in {@link android.os.Build.VERSION_CODES#KITKAT}, read access requires the
      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
      * which is automatically granted if you hold the write permission.
      * <p>
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index e7dfbd7..97ee90d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -865,7 +865,12 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * Gets the list of shared/external storage volumes available to the current user.
+     *
+     * <p>It always contains the primary storage volume, plus any additional external volume(s)
+     * available in the device, such as SD cards or attached USB drives.
+     */
     public @NonNull StorageVolume[] getVolumeList() {
         return getVolumeList(mContext.getUserId(), 0);
     }
@@ -914,7 +919,9 @@
         return paths;
     }
 
-    /** {@hide} */
+    /**
+     * Gets the primary shared/external storage volume available to the current user.
+     */
     public @NonNull StorageVolume getPrimaryVolume() {
         return getPrimaryVolume(getVolumeList());
     }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1408202..60e1266 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,11 +16,17 @@
 
 package android.os.storage;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.Intent;
 import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.DocumentsContract;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -29,14 +35,46 @@
 import java.io.File;
 
 /**
- * Information about a storage volume that may be mounted. This is a legacy
- * specialization of {@link VolumeInfo} which describes the volume for a
- * specific user.
- * <p>
- * This class may be deprecated in the future.
+ * Information about a shared/external storage volume for a specific user.
  *
- * @hide
+ * <p>
+ * A device always has one (and one only) primary storage volume, but it could have extra volumes,
+ * like SD cards and USB drives. This object represents the logical view of a storage
+ * volume for a specific user: different users might have different views for the same physical
+ * volume (for example, if the volume is a built-in emulated storage).
+ *
+ * <p>
+ * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
+ * verify its state.
+ *
+ * <p>
+ * Applications willing to read or write to this storage volume needs to get a permission from the
+ * user first, which can be achieved in the following ways:
+ *
+ * <ul>
+ * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
+ * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
+ * simpler API and narrows the access to the given directory (and its descendants).
+ * <li>To get access to any directory (and its descendants), they can use the Storage Area Framework
+ * APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and {@link Intent#ACTION_OPEN_DOCUMENT_TREE},
+ * although these APIs do not guarantee the user will select this specific volume.
+ * <li>To get read and write access to the primary storage volume, applications can declare the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
+ * latter including the former. This approach is discouraged, since users may be hesitant to grant
+ * broad access to all files contained on a storage device.
+ * </ul>
+ *
+ * <p>It can be obtained through {@link StorageManager#getVolumeList()} and
+ * {@link StorageManager#getPrimaryVolume()} and also as an extra in some broadcasts
+ * (see {@link #EXTRA_STORAGE_VOLUME}).
+ *
+ * <p>
+ * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
+ * storage semantics.
  */
+// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
+// user, but is now part of the public API.
 public class StorageVolume implements Parcelable {
 
     private final String mId;
@@ -53,14 +91,23 @@
     private final String mFsUuid;
     private final String mState;
 
-    // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
-    // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
-    // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
-    public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
+    /**
+     * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
+     * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
+     * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
+     * contains a {@link StorageVolume}.
+     */
+    // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
+    public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
 
+    /** {@hide} */
     public static final int STORAGE_ID_INVALID = 0x00000000;
+    /** {@hide} */
     public static final int STORAGE_ID_PRIMARY = 0x00010001;
 
+    /** {@hide} */
     public 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) {
@@ -95,6 +142,7 @@
         mState = in.readString();
     }
 
+    /** {@hide} */
     public String getId() {
         return mId;
     }
@@ -103,17 +151,19 @@
      * Returns the mount path for the volume.
      *
      * @return the mount path
+     * @hide
      */
     public String getPath() {
         return mPath.toString();
     }
 
+    /** {@hide} */
     public File getPathFile() {
         return mPath;
     }
 
     /**
-     * Returns a user visible description of the volume.
+     * Returns a user-visible description of the volume.
      *
      * @return the volume description
      */
@@ -121,6 +171,10 @@
         return mDescription;
     }
 
+    /**
+     * Returns true if the volume is the primary shared/external storage, which is the volume
+     * backed by {@link Environment#getExternalStorageDirectory()}.
+     */
     public boolean isPrimary() {
         return mPrimary;
     }
@@ -148,6 +202,7 @@
      * this is also used for the storage_id column in the media provider.
      *
      * @return MTP storage ID
+     * @hide
      */
     public int getStorageId() {
         return mStorageId;
@@ -164,6 +219,7 @@
      * too close to full.
      *
      * @return MTP reserve space
+     * @hide
      */
     public int getMtpReserveSpace() {
         return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
@@ -173,6 +229,7 @@
      * Returns true if this volume can be shared via USB mass storage.
      *
      * @return whether mass storage is allowed
+     * @hide
      */
     public boolean allowMassStorage() {
         return mAllowMassStorage;
@@ -182,22 +239,28 @@
      * Returns maximum file size for the volume, or zero if it is unbounded.
      *
      * @return maximum file size
+     * @hide
      */
     public long getMaxFileSize() {
         return mMaxFileSize;
     }
 
+    /** {@hide} */
     public UserHandle getOwner() {
         return mOwner;
     }
 
-    public String getUuid() {
+    /**
+     * Gets the volume UUID, if any.
+     */
+    public @Nullable String getUuid() {
         return mFsUuid;
     }
 
     /**
      * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
      * parse or UUID is unknown.
+     * @hide
      */
     public int getFatVolumeId() {
         if (mFsUuid == null || mFsUuid.length() != 9) {
@@ -210,14 +273,56 @@
         }
     }
 
+    /** {@hide} */
     public String getUserLabel() {
         return mDescription;
     }
 
+    /**
+     * Returns the current state of the volume.
+     *
+     * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
+     *         {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
+     *         {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
+     *         {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
+     *         {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
+     */
     public String getState() {
         return mState;
     }
 
+    /**
+     * Builds an intent to give access to a standard storage directory after obtaining the user's
+     * approval.
+     * <p>
+     * When invoked, the system will ask the user to grant access to the requested directory (and
+     * its descendants). The result of the request will be returned to the activity through the
+     * {@code onActivityResult} method.
+     * <p>
+     * To gain access to descendants (child, grandchild, etc) documents, use
+     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
+     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
+     *
+     * <b>If your application only needs to store internal data, consider using
+     * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
+     * {@link Context#getExternalCacheDirs()}, or
+     * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
+     *
+     * @param directoryName must be one of
+     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
+     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
+     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
+     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
+     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
+     *
+     * @see DocumentsContract
+     */
+    public Intent createAccessIntent(@NonNull String directoryName) {
+        final Intent intent = new Intent(Intent.ACTION_OPEN_EXTERNAL_DIRECTORY);
+        intent.setData(Uri.fromFile(new File(mPath, directoryName)));
+        return intent;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (obj instanceof StorageVolume && mPath != null) {
@@ -234,11 +339,23 @@
 
     @Override
     public String toString() {
+        final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
+        if (mFsUuid != null) {
+            buffer.append(" (").append(mFsUuid).append(")");
+        }
+        return buffer.toString();
+    }
+
+    /** {@hide} */
+    // TODO(b/26742218): find out where toString() is called internally and replace these calls by
+    // dump().
+    public String dump() {
         final CharArrayWriter writer = new CharArrayWriter();
         dump(new IndentingPrintWriter(writer, "    ", 80));
         return writer.toString();
     }
 
+    /** {@hide} */
     public void dump(IndentingPrintWriter pw) {
         pw.println("StorageVolume:");
         pw.increaseIndent();
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index b7f071d..3700098 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -39,6 +39,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
 import android.os.RemoteException;
+import android.os.storage.StorageVolume;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -62,7 +63,8 @@
  * All client apps must hold a valid URI permission grant to access documents,
  * typically issued when a user makes a selection through
  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
- * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or {@link Intent#ACTION_OPEN_EXTERNAL_DIRECTORY}.
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
+ * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
  *
  * @see DocumentsProvider
  */