Browse mode for DocumentsUI, removed volume state.

The existing management mode is too specific, and requires that
storage backends add queryChildDocumentsForManage(), etc.  Instead,
to offer more natural browsing support, add a new BROWSE_ROOT intent.

It behaves mostly like MANAGE_ROOT, except that it doesn't mutate
its Uris with setManageMode(), and it shortcuts straight to VIEW on
clicked documents.

It can be launched like this:

$ adb shell am start -a android.provider.action.BROWSE_ROOT
    -d content://com.android.externalstorage.documents/root/8405-1DFB
    -c android.intent.category.DEFAULT

Also rename a MetricsConstants to make it clearer, and don't
auto-mount all emulated volumes.

Fix bugs around parceling of DiskInfo/VolumeInfo.  Method to resolve
the best description for a VolumeInfo, which might need to fall
back to DiskInfo.

Add back "removed" volume state so we send broadcast when a volume
is destroyed, matching the expected public API behavior.

Bug: 19993667
Change-Id: I13aff32c5e11dfc63da44aee9e93a27f4690a43f
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index 56c55f1..4704b67 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -16,7 +16,7 @@
 
 package android.os.storage;
 
-import android.content.Context;
+import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.DebugUtils;
@@ -57,12 +57,12 @@
         volumes = parcel.readStringArray();
     }
 
-    public String getDescription(Context context) {
+    public String getDescription() {
         // TODO: splice vendor label into these strings
         if ((flags & FLAG_SD) != 0) {
-            return context.getString(com.android.internal.R.string.storage_sd_card);
+            return Resources.getSystem().getString(com.android.internal.R.string.storage_sd_card);
         } else if ((flags & FLAG_USB) != 0) {
-            return context.getString(com.android.internal.R.string.storage_usb);
+            return Resources.getSystem().getString(com.android.internal.R.string.storage_usb);
         } else {
             return null;
         }
@@ -119,7 +119,7 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeString(id);
-        parcel.writeInt(flags);
+        parcel.writeInt(this.flags);
         parcel.writeLong(size);
         parcel.writeString(label);
         parcel.writeStringArray(volumes);
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 5a5d8b7..bd42f6a 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -30,10 +30,12 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
 import java.io.File;
@@ -454,6 +456,18 @@
     }
 
     /** {@hide} */
+    public @Nullable DiskInfo findDiskByVolumeId(String volId) {
+        Preconditions.checkNotNull(volId);
+        // TODO; go directly to service to make this faster
+        for (DiskInfo disk : getDisks()) {
+            if (ArrayUtils.contains(disk.volumes, volId)) {
+                return disk;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
     public @Nullable VolumeInfo findVolumeById(String id) {
         Preconditions.checkNotNull(id);
         // TODO; go directly to service to make this faster
@@ -487,6 +501,23 @@
     }
 
     /** {@hide} */
+    public @Nullable String getBestVolumeDescription(String volId) {
+        String descrip = null;
+
+        final VolumeInfo vol = findVolumeById(volId);
+        if (vol != null) {
+            descrip = vol.getDescription();
+        }
+
+        final DiskInfo disk = findDiskByVolumeId(volId);
+        if (disk != null && TextUtils.isEmpty(descrip)) {
+            descrip = disk.getDescription();
+        }
+
+        return descrip;
+    }
+
+    /** {@hide} */
     public void mount(String volId) {
         try {
             mMountService.mount(volId);
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 2dc0361..beca8b8 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.mtp.MtpStorage;
 import android.os.Environment;
 import android.os.Parcel;
@@ -44,6 +45,8 @@
  * @hide
  */
 public class VolumeInfo implements Parcelable {
+    /** Stub volume representing internal private storage */
+    public static final String ID_PRIVATE_INTERNAL = "private";
     /** Real volume representing internal emulated storage */
     public static final String ID_EMULATED_INTERNAL = "emulated";
 
@@ -59,6 +62,7 @@
     public static final int STATE_FORMATTING = 3;
     public static final int STATE_UNMOUNTING = 4;
     public static final int STATE_UNMOUNTABLE = 5;
+    public static final int STATE_REMOVED = 6;
 
     public static final int FLAG_PRIMARY = 1 << 0;
     public static final int FLAG_VISIBLE = 1 << 1;
@@ -73,12 +77,14 @@
         sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
+        sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
 
         sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
         sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
         sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
         sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
         sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
     }
 
     /** vold state */
@@ -96,8 +102,6 @@
     public final int mtpIndex;
     public String nickname;
 
-    public DiskInfo disk;
-
     public VolumeInfo(String id, int type, int mtpIndex) {
         this.id = Preconditions.checkNotNull(id);
         this.type = type;
@@ -135,9 +139,9 @@
         return getBroadcastForEnvironment(getEnvironmentForState(state));
     }
 
-    public String getDescription(Context context) {
-        if (ID_EMULATED_INTERNAL.equals(id)) {
-            return context.getString(com.android.internal.R.string.storage_internal);
+    public @Nullable String getDescription() {
+        if (ID_PRIVATE_INTERNAL.equals(id)) {
+            return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
         } else if (!TextUtils.isEmpty(nickname)) {
             return nickname;
         } else if (!TextUtils.isEmpty(fsLabel)) {
@@ -189,7 +193,7 @@
             userPath = new File("/dev/null");
         }
 
-        String description = getDescription(context);
+        String description = getDescription();
         if (description == null) {
             description = context.getString(android.R.string.unknownName);
         }
@@ -283,7 +287,7 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeString(id);
         parcel.writeInt(type);
-        parcel.writeInt(flags);
+        parcel.writeInt(this.flags);
         parcel.writeInt(userId);
         parcel.writeInt(state);
         parcel.writeString(fsType);
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 9a0858a..b0a600b 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -106,6 +106,9 @@
     /** {@hide} */
     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
 
+    /** {@hide} */
+    public static final String ACTION_BROWSE_ROOT = "android.provider.action.BROWSE_ROOT";
+
     /**
      * Buffer is large enough to rewind past any EXIF headers.
      */
diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java
index ee225a1..6aa81ce 100644
--- a/core/java/com/android/internal/logging/MetricsConstants.java
+++ b/core/java/com/android/internal/logging/MetricsConstants.java
@@ -69,7 +69,9 @@
     public static final int DEVELOPMENT = 39;
     public static final int DEVICEINFO = 40;
     public static final int DEVICEINFO_IMEI_INFORMATION = 41;
+    @Deprecated
     public static final int DEVICEINFO_MEMORY = 42;
+    public static final int DEVICEINFO_STORAGE = 42;
     public static final int DEVICEINFO_SIM_STATUS = 43;
     public static final int DEVICEINFO_STATUS = 44;
     public static final int DEVICEINFO_USB = 45;
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f908fcb..62e724a 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.util;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.ArraySet;
 
 import dalvik.system.VMRuntime;
@@ -24,13 +26,13 @@
 
 import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * ArrayUtils contains some methods that you can call to find out
  * the most efficient increments by which to grow arrays.
  */
-public class ArrayUtils
-{
+public class ArrayUtils {
     private static final int CACHE_SIZE = 73;
     private static Object[] sCache = new Object[CACHE_SIZE];
 
@@ -158,11 +160,7 @@
     public static <T> int indexOf(T[] array, T value) {
         if (array == null) return -1;
         for (int i = 0; i < array.length; i++) {
-            if (array[i] == null) {
-                if (value == null) return i;
-            } else {
-                if (value != null && array[i].equals(value)) return i;
-            }
+            if (Objects.equals(array[i], value)) return i;
         }
         return -1;
     }
@@ -209,17 +207,15 @@
     }
 
     /**
-     * Appends an element to a copy of the array and returns the copy.
-     * @param array The original array, or null to represent an empty array.
-     * @param element The element to add.
-     * @return A new array that contains all of the elements of the original array
-     * with the specified element added at the end.
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
      */
     @SuppressWarnings("unchecked")
-    public static <T> T[] appendElement(Class<T> kind, T[] array, T element) {
+    public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element) {
         final T[] result;
         final int end;
         if (array != null) {
+            if (contains(array, element)) return array;
             end = array.length;
             result = (T[])Array.newInstance(kind, end + 1);
             System.arraycopy(array, 0, result, 0, end);
@@ -232,21 +228,15 @@
     }
 
     /**
-     * Removes an element from a copy of the array and returns the copy.
-     * If the element is not present, then the original array is returned unmodified.
-     * @param array The original array, or null to represent an empty array.
-     * @param element The element to remove.
-     * @return A new array that contains all of the elements of the original array
-     * except the first copy of the specified element removed.  If the specified element
-     * was not present, then returns the original array.  Returns null if the result
-     * would be an empty array.
+     * Removes value from given array if present, providing set-like behavior.
      */
     @SuppressWarnings("unchecked")
-    public static <T> T[] removeElement(Class<T> kind, T[] array, T element) {
+    public static @Nullable <T> T[] removeElement(Class<T> kind, @Nullable T[] array, T element) {
         if (array != null) {
+            if (!contains(array, element)) return array;
             final int length = array.length;
             for (int i = 0; i < length; i++) {
-                if (array[i] == element) {
+                if (Objects.equals(array[i], element)) {
                     if (length == 1) {
                         return null;
                     }
@@ -261,14 +251,10 @@
     }
 
     /**
-     * Appends a new value to a copy of the array and returns the copy.  If
-     * the value is already present, the original array is returned
-     * @param cur The original array, or null to represent an empty array.
-     * @param val The value to add.
-     * @return A new array that contains all of the values of the original array
-     * with the new value added, or the original array.
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
      */
-    public static int[] appendInt(int[] cur, int val) {
+    public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
         if (cur == null) {
             return new int[] { val };
         }
@@ -284,7 +270,10 @@
         return ret;
     }
 
-    public static int[] removeInt(int[] cur, int val) {
+    /**
+     * Removes value from given array if present, providing set-like behavior.
+     */
+    public static @Nullable int[] removeInt(@Nullable int[] cur, int val) {
         if (cur == null) {
             return null;
         }
@@ -305,14 +294,10 @@
     }
 
     /**
-     * Appends a new value to a copy of the array and returns the copy.  If
-     * the value is already present, the original array is returned
-     * @param cur The original array, or null to represent an empty array.
-     * @param val The value to add.
-     * @return A new array that contains all of the values of the original array
-     * with the new value added, or the original array.
+     * Adds value to given array if not already present, providing set-like
+     * behavior.
      */
-    public static long[] appendLong(long[] cur, long val) {
+    public static @NonNull long[] appendLong(@Nullable long[] cur, long val) {
         if (cur == null) {
             return new long[] { val };
         }
@@ -328,7 +313,10 @@
         return ret;
     }
 
-    public static long[] removeLong(long[] cur, long val) {
+    /**
+     * Removes value from given array if present, providing set-like behavior.
+     */
+    public static @Nullable long[] removeLong(@Nullable long[] cur, long val) {
         if (cur == null) {
             return null;
         }