StorageManager:  Clean up and generalize storage configuration resources

Replace config_emulateExternalStorage, config_externalStorageRemovable,
config_externalStoragePaths, config_externalStorageDescriptions and
config_mtpReserveSpaceMegabytes resources with an XML resource file
to describe the external storages that are available.

Add android.os.storage.StorageVolume class

StorageManager.getVolumeList() now returns an array of StorageVolume

Change-Id: I06ce1451ebf08b82f0ee825d56d59ebf72eacd3d
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e308c2c..1f3f6d9 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -20,6 +20,7 @@
 
 import android.content.res.Resources;
 import android.os.storage.IMountService;
+import android.os.storage.StorageVolume;
 import android.util.Log;
 
 /**
@@ -35,7 +36,25 @@
 
     private static final Object mLock = new Object();
 
-    private volatile static Boolean mIsExternalStorageEmulated = null;
+    private volatile static StorageVolume mPrimaryVolume = null;
+
+    private static StorageVolume getPrimaryVolume() {
+        if (mPrimaryVolume == null) {
+            synchronized (mLock) {
+                if (mPrimaryVolume == null) {
+                    try {
+                        IMountService mountService = IMountService.Stub.asInterface(ServiceManager
+                                .getService("mount"));
+                        Parcelable[] volumes = mountService.getVolumeList();
+                        mPrimaryVolume = (StorageVolume)volumes[0];
+                    } catch (Exception e) {
+                        Log.e(TAG, "couldn't talk to MountService", e);
+                    }
+                }
+            }
+        }
+        return mPrimaryVolume;
+    }
 
     /**
      * Gets the Android root directory.
@@ -416,9 +435,8 @@
      * <p>See {@link #getExternalStorageDirectory()} for more information.
      */
     public static boolean isExternalStorageRemovable() {
-        if (isExternalStorageEmulated()) return false;
-        return Resources.getSystem().getBoolean(
-                com.android.internal.R.bool.config_externalStorageRemovable);
+        StorageVolume volume = getPrimaryVolume();
+        return (volume != null && volume.isRemovable());
     }
 
     /**
@@ -435,23 +453,8 @@
      * android.content.ComponentName, boolean)} for additional details.
      */
     public static boolean isExternalStorageEmulated() {
-        if (mIsExternalStorageEmulated == null) {
-            synchronized (mLock) {
-                if (mIsExternalStorageEmulated == null) {
-                    boolean externalStorageEmulated;
-                    try {
-                        IMountService mountService = IMountService.Stub.asInterface(ServiceManager
-                                .getService("mount"));
-                        externalStorageEmulated = mountService.isExternalStorageEmulated();
-                        mIsExternalStorageEmulated = Boolean.valueOf(externalStorageEmulated);
-                    } catch (Exception e) {
-                        Log.e(TAG, "couldn't talk to MountService", e);
-                        return false;
-                    }
-                }
-            }
-        }
-        return mIsExternalStorageEmulated;
+        StorageVolume volume = getPrimaryVolume();
+        return (volume != null && volume.isEmulated());
     }
 
     static File getDirectory(String variableName, String defaultPath) {
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 27da3c3..c2dc8ae 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -20,7 +20,9 @@
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.storage.StorageVolume;
 
 /**
  * WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -638,15 +640,15 @@
                 return _result;
             }
 
-            public String[] getVolumeList() throws RemoteException {
+            public Parcelable[] getVolumeList() throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
-                String[] _result;
+                Parcelable[] _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
                     mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
                     _reply.readException();
-                    _result = _reply.readStringArray();
+                    _result = _reply.readParcelableArray(StorageVolume.class.getClassLoader());
                 } finally {
                     _reply.recycle();
                     _data.recycle();
@@ -1024,9 +1026,9 @@
                 }
                 case TRANSACTION_getVolumeList: {
                     data.enforceInterface(DESCRIPTOR);
-                    String[] result = getVolumeList();
+                    Parcelable[] result = getVolumeList();
                     reply.writeNoException();
-                    reply.writeStringArray(result);
+                    reply.writeParcelableArray(result, 0);
                     return true;
                 }
             }
@@ -1207,5 +1209,5 @@
     /**
      * Returns list of all mountable volumes.
      */
-    public String[] getVolumeList() throws RemoteException;
+    public Parcelable[] getVolumeList() throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 234057b..6fd1d00 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -19,6 +19,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
@@ -545,12 +546,34 @@
      * Returns list of all mountable volumes.
      * @hide
      */
-    public String[] getVolumeList() {
+    public StorageVolume[] getVolumeList() {
         try {
-            return mMountService.getVolumeList();
+            Parcelable[] list = mMountService.getVolumeList();
+            if (list == null) return new StorageVolume[0];
+            int length = list.length;
+            StorageVolume[] result = new StorageVolume[length];
+            for (int i = 0; i < length; i++) {
+                result[i] = (StorageVolume)list[i];
+            }
+            return result;
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to get volume list", e);
             return null;
         }
     }
+
+    /**
+     * Returns list of paths for all mountable volumes.
+     * @hide
+     */
+    public String[] getVolumePaths() {
+        StorageVolume[] volumes = getVolumeList();
+        if (volumes == null) return null;
+        int count = volumes.length;
+        String[] paths = new String[count];
+        for (int i = 0; i < count; i++) {
+            paths[i] = volumes[i].getPath();
+        }
+        return paths;
+    }
 }
diff --git a/core/java/android/os/storage/StorageVolume.aidl b/core/java/android/os/storage/StorageVolume.aidl
new file mode 100644
index 0000000..d689917
--- /dev/null
+++ b/core/java/android/os/storage/StorageVolume.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+parcelable StorageVolume;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
new file mode 100644
index 0000000..d79f6c8
--- /dev/null
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A class representing a storage volume
+ * @hide
+ */
+public class StorageVolume implements Parcelable {
+
+    private static final String TAG = "StorageVolume";
+
+    private final String mPath;
+    private final String mDescription;
+    private final boolean mRemovable;
+    private final boolean mEmulated;
+    private final int mMtpReserveSpace;
+
+    public StorageVolume(String path, String description,
+            boolean removable, boolean emulated,
+            int mtpReserveSpace) {
+        mPath = path;
+        mDescription = description;
+        mRemovable = removable;
+        mEmulated = emulated;
+        mMtpReserveSpace = mtpReserveSpace;
+    }
+
+    /**
+     * Returns the mount path for the volume.
+     *
+     * @return the mount path
+     */
+    public String getPath() {
+        return mPath;
+    }
+
+    /**
+     * Returns a user visible description of the volume.
+     *
+     * @return the volume description
+     */
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Returns true if the volume is removable.
+     *
+     * @return is removable
+     */
+    public boolean isRemovable() {
+        return mRemovable;
+    }
+
+    /**
+     * Returns true if the volume is emulated.
+     *
+     * @return is removable
+     */
+    public boolean isEmulated() {
+        return mEmulated;
+    }
+
+    /**
+     * Number of megabytes of space to leave unallocated by MTP.
+     * MTP will subtract this value from the free space it reports back
+     * to the host via GetStorageInfo, and will not allow new files to
+     * be added via MTP if there is less than this amount left free in the storage.
+     * If MTP has dedicated storage this value should be zero, but if MTP is
+     * sharing storage with the rest of the system, set this to a positive value
+     * to ensure that MTP activity does not result in the storage being
+     * too close to full.
+     *
+     * @return MTP reserve space
+     */
+    public int getMtpReserveSpace() {
+        return mMtpReserveSpace;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof StorageVolume && mPath != null) {
+            StorageVolume volume = (StorageVolume)obj;
+            return (mPath.equals(volume.mPath));
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mPath.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return mPath;
+    }
+
+    public static final Parcelable.Creator<StorageVolume> CREATOR =
+        new Parcelable.Creator<StorageVolume>() {
+        public StorageVolume createFromParcel(Parcel in) {
+            String path = in.readString();
+            String description = in.readString();
+            int removable = in.readInt();
+            int emulated = in.readInt();
+            int mtpReserveSpace = in.readInt();
+            return new StorageVolume(path, description,
+                    removable == 1, emulated == 1, mtpReserveSpace);
+        }
+
+        public StorageVolume[] newArray(int size) {
+            return new StorageVolume[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mPath);
+        parcel.writeString(mDescription);
+        parcel.writeInt(mRemovable ? 1 : 0);
+        parcel.writeInt(mEmulated ? 1 : 0);
+        parcel.writeInt(mMtpReserveSpace);
+    }
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 71a8b2a..004b755 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4878,4 +4878,21 @@
         <!-- Y coordinate of the icon hot spot. -->
         <attr name="hotSpotY" format="float" />
     </declare-styleable>
+
+    <declare-styleable name="Storage">
+        <!-- path to mount point for the storage --> 
+        <attr name="mountPoint" format="string" />
+        <!-- user visible description of the storage -->
+        <attr name="storageDescription" format="string" />
+        <!-- true if the storage is the primary external storage -->
+        <attr name="primary" format="boolean" />
+        <!-- true if the storage is removable -->
+        <attr name="removable" format="boolean" />
+        <!-- true if the storage is emulated via the FUSE sdcard daemon -->
+        <attr name="emulated" format="boolean" />
+        <!-- number of megabytes of storage MTP should reserve for free storage
+             (used for emulated storage that is shared with system's data partition) -->
+        <attr name="mtpReserve" format="integer" />
+    </declare-styleable>
+
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 27c7a4d..49bbd82 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -89,52 +89,10 @@
          when there's no network connection. If the scan doesn't timeout, use zero -->
     <integer name="config_radioScanningTimeout">0</integer>
 
-    <!-- Set to true if the location returned Environment.getExternalStorageDirectory()
-         is actually a subdirectory of the internal storage.
-         If this is set then Environment.getExternalStorageState() will always return
-         MEDIA_MOUNTED and Intent.ACTION_MEDIA_MOUNTED will be broadcast at boot time
-         for backward compatibility with apps that require external storage. -->
-    <bool name="config_emulateExternalStorage">false</bool>
-
-    <!-- Set to true if external storage is case sensitive.
-         Typically external storage is FAT, which is case insensitive. -->
-    <bool name="config_caseSensitiveExternalStorage">false</bool>
-    
-    <!-- A product with no SD card == not removable. -->
-    <bool name="config_externalStorageRemovable" product="nosdcard">false</bool>
-    <!-- Configures whether the primary external storage device is
-         removable.  For example, if external storage is on an SD card,
-         it is removable; if it is built in to the device, it is not removable.
-         The default product has external storage on an SD card, which is
-         removable. -->
-    <bool name="config_externalStorageRemovable" product="default">true</bool>
-
-    <!-- List of mount points for external storage devices.
-         The first item on the list should be the primary external storage and should match the
-         value returned by Environment.getExternalStorageDirectory (/mnt/sdcard).
-         MTP storage IDs will be generated based on the position of the mountpoint in this list:
-            0x00010001 - ID for primary external storage (/mnt/sdcard)
-            0x00020001 - ID for first secondary external storage
-            0x00030001 - ID for second secondary external storage
-         etc. -->
-    <string-array translatable="false" name="config_externalStoragePaths">
-        <item>"/mnt/sdcard"</item>
-    </string-array>
-
-    <!-- User visible descriptions of the volumes in the config_externalStoragePaths array. -->
-    <string-array translatable="true" name="config_externalStorageDescriptions">
-        <item>"SD card"</item>
-    </string-array>
-
-    <!-- Number of megabytes of space to leave unallocated by MTP.
-         MTP will subtract this value from the free space it reports back
-         to the host via GetStorageInfo, and will not allow new files to
-         be added via MTP if there is less than this amount left free in the storage.
-         If MTP has dedicated storage this value should be zero, but if MTP is
-         sharing storage with the rest of the system, set this to a positive value
-         to ensure that MTP activity does not result in the storage being
-         too close to full. -->
-    <integer name="config_mtpReserveSpaceMegabytes">0</integer>
+    <!-- Storage lists for default and nosdcard products.
+         Both have a single SD card storage, but the storage is not removable in the nosdcard case -->
+    <integer name="config_storageListId" product="nosdcard">@xml/storage_list_nosdcard</integer>
+    <integer name="config_storageListId" product="default">@xml/storage_list</integer>
 
     <!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
          Please don't copy them, copy anything else. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8ef9a3b..b713b51 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2808,4 +2808,12 @@
     <!-- Description of the button to decrement the NumberPicker value. [CHAR LIMIT=NONE] -->
     <string name="number_picker_decrement_button">Decrement</string>
 
+    <!-- Storage description for internal storage. [CHAR LIMIT=NONE] -->
+    <string name="storage_internal">Internal Storage</string>
+
+    <!-- Storage description for the SD card. [CHAR LIMIT=NONE] -->
+    <string name="storage_sd_card">SD Card</string>
+
+    <!-- Storage description for USB storage. [CHAR LIMIT=NONE] -->
+    <string name="storage_usb">USB storage</string>
 </resources>
diff --git a/core/res/res/xml/storage_list.xml b/core/res/res/xml/storage_list.xml
new file mode 100644
index 0000000..944bb3a
--- /dev/null
+++ b/core/res/res/xml/storage_list.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- The <device> element should contain one or more <storage> elements.
+     Exactly one of these should have the attribute primary="true".
+     This storage will be the primary external storage and should have mountPoint="/mnt/sdcard".
+     Each storage should have both a mountPoint and storageDescription attribute.
+     The following attributes are optional:
+
+        primary:    (boolean) this storage is the primary external storage
+        removable:  (boolean) this is removable storage (for example, a real SD card)
+        emulated:   (boolean) the storage is emulated via the FUSE sdcard daemon
+        mtpReserve: (integer) number of megabytes of storage MTP should reserve for free storage
+                     (used for emulated storage that is shared with system's data partition)
+
+      A storage should not have both emulated and removable set to true
+-->
+
+<StorageList xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- removable is not set in nosdcard product -->
+    <storage android:mountPoint="/mnt/sdcard"
+    		 android:storageDescription="@string/storage_usb"
+    		 android:primary="true" />
+</StorageList>