From 2f6a3885533a52758c2cd4f81f6123a712be8ae6 Mon Sep 17 00:00:00 2001 From: Mike Lockwood Date: Mon, 9 May 2011 19:08:06 -0700 Subject: [PATCH] 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 --- core/java/android/os/Environment.java | 45 +++--- .../android/os/storage/IMountService.java | 14 +- .../android/os/storage/StorageManager.java | 27 +++- .../android/os/storage/StorageVolume.aidl | 19 +++ .../android/os/storage/StorageVolume.java | 147 ++++++++++++++++++ core/res/res/values/attrs.xml | 17 ++ core/res/res/values/config.xml | 50 +----- core/res/res/values/strings.xml | 8 + core/res/res/xml/storage_list.xml | 40 +++++ media/java/android/media/MediaScanner.java | 3 +- .../java/com/android/server/MountService.java | 109 +++++++++++-- 11 files changed, 389 insertions(+), 90 deletions(-) create mode 100644 core/java/android/os/storage/StorageVolume.aidl create mode 100644 core/java/android/os/storage/StorageVolume.java create mode 100644 core/res/res/xml/storage_list.xml diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index e308c2c48a8d8..1f3f6d9565b6c 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -20,6 +20,7 @@ import java.io.File; import android.content.res.Resources; import android.os.storage.IMountService; +import android.os.storage.StorageVolume; import android.util.Log; /** @@ -35,7 +36,25 @@ public class Environment { 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 @@ public class Environment { *

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 @@ public class Environment { * 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 27da3c3448809..c2dc8aeefc435 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -20,7 +20,9 @@ import android.os.Binder; 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 @@ public interface IMountService extends IInterface { 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 @@ public interface IMountService extends IInterface { } 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 @@ public interface IMountService extends IInterface { /** * 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 234057b8632e3..6fd1d002fa847 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -19,6 +19,7 @@ package android.os.storage; 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 @@ public class StorageManager * 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 0000000000000..d689917905191 --- /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 0000000000000..d79f6c8f17924 --- /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 CREATOR = + new Parcelable.Creator() { + 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 71a8b2a82dc18..004b7555f53fd 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4878,4 +4878,21 @@ + + + + + + + + + + + + + + + + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 27c7a4d1a602a..49bbd824418c7 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 --> 0 - - false - - - false - - - false - - true - - - - "/mnt/sdcard" - - - - - "SD card" - - - - 0 + + @xml/storage_list_nosdcard + @xml/storage_list diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 8ef9a3b247b47..b713b518e7395 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2808,4 +2808,12 @@ Decrement + + Internal Storage + + + SD Card + + + USB storage diff --git a/core/res/res/xml/storage_list.xml b/core/res/res/xml/storage_list.xml new file mode 100644 index 0000000000000..944bb3a942091 --- /dev/null +++ b/core/res/res/xml/storage_list.xml @@ -0,0 +1,40 @@ + + + + + + + + + diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index d1eb3884968ef..55b0045179585 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -1147,8 +1147,7 @@ public class MediaScanner mGenresUri = Genres.getContentUri(volumeName); mPlaylistsUri = Playlists.getContentUri(volumeName); - mCaseInsensitivePaths = !mContext.getResources().getBoolean( - com.android.internal.R.bool.config_caseSensitiveExternalStorage); + mCaseInsensitivePaths = true; if (!Process.supportsProcesses()) { // Simulator uses host file system, so it should be case sensitive. mCaseInsensitivePaths = false; diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index a100f1f34dd8f..376d42f74c150 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -17,6 +17,7 @@ package com.android.server; import com.android.internal.app.IMediaContainerService; +import com.android.internal.util.XmlUtils; import com.android.server.am.ActivityManagerService; import android.Manifest; @@ -28,6 +29,9 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.res.ObbInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; import android.net.Uri; import android.os.Binder; import android.os.Environment; @@ -36,6 +40,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -46,8 +51,14 @@ import android.os.storage.IMountShutdownObserver; import android.os.storage.IObbActionListener; import android.os.storage.OnObbStateChangeListener; import android.os.storage.StorageResultCode; +import android.os.storage.StorageVolume; import android.text.TextUtils; +import android.util.AttributeSet; import android.util.Slog; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import java.io.FileDescriptor; import java.io.IOException; @@ -145,6 +156,8 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC private Context mContext; private NativeDaemonConnector mConnector; + private final ArrayList mVolumes = new ArrayList(); + private StorageVolume mPrimaryVolume; private final HashMap mVolumeStates = new HashMap(); private String mExternalStoragePath; private PackageManagerService mPms; @@ -1068,6 +1081,74 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC } } + // Storage list XML tags + private static final String TAG_STORAGE_LIST = "StorageList"; + private static final String TAG_STORAGE = "storage"; + + private void readStorageList(Resources resources) { + int id = com.android.internal.R.xml.storage_list; + XmlResourceParser parser = resources.getXml(id); + AttributeSet attrs = Xml.asAttributeSet(parser); + + try { + XmlUtils.beginDocument(parser, TAG_STORAGE_LIST); + while (true) { + XmlUtils.nextElement(parser); + + String element = parser.getName(); + if (element == null) break; + + if (TAG_STORAGE.equals(element)) { + TypedArray a = resources.obtainAttributes(attrs, + com.android.internal.R.styleable.Storage); + + CharSequence path = a.getText( + com.android.internal.R.styleable.Storage_mountPoint); + CharSequence description = a.getText( + com.android.internal.R.styleable.Storage_storageDescription); + boolean primary = a.getBoolean( + com.android.internal.R.styleable.Storage_primary, false); + boolean removable = a.getBoolean( + com.android.internal.R.styleable.Storage_removable, false); + boolean emulated = a.getBoolean( + com.android.internal.R.styleable.Storage_emulated, false); + int mtpReserve = a.getInt( + com.android.internal.R.styleable.Storage_mtpReserve, 0); + + Slog.d(TAG, "got storage path: " + path + " description: " + description + + " primary: " + primary + " removable: " + removable + + " emulated: " + emulated + " mtpReserve: " + mtpReserve); + if (path == null || description == null) { + Slog.e(TAG, "path or description is null in readStorageList"); + } else { + StorageVolume volume = new StorageVolume(path.toString(), + description.toString(), removable, emulated, mtpReserve); + if (primary) { + if (mPrimaryVolume == null) { + mPrimaryVolume = volume; + } else { + Slog.e(TAG, "multiple primary volumes in storage list"); + } + } + if (mPrimaryVolume == volume) { + // primay volume must be first + mVolumes.add(0, volume); + } else { + mVolumes.add(volume); + } + } + a.recycle(); + } + } + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + parser.close(); + } + } + /** * Constructs a new MountService instance * @@ -1075,13 +1156,16 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC */ public MountService(Context context) { mContext = context; + Resources resources = context.getResources(); + readStorageList(resources); - mExternalStoragePath = Environment.getExternalStorageDirectory().getPath(); - mEmulateExternalStorage = context.getResources().getBoolean( - com.android.internal.R.bool.config_emulateExternalStorage); - if (mEmulateExternalStorage) { - Slog.d(TAG, "using emulated external storage"); - mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED); + if (mPrimaryVolume != null) { + mExternalStoragePath = mPrimaryVolume.getPath(); + mEmulateExternalStorage = mPrimaryVolume.isEmulated(); + if (mEmulateExternalStorage) { + Slog.d(TAG, "using emulated external storage"); + mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED); + } } // XXX: This will go away soon in favor of IMountServiceObserver @@ -1753,13 +1837,12 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC } } - public String[] getVolumeList() { - synchronized(mVolumeStates) { - Set volumes = mVolumeStates.keySet(); - String[] result = new String[volumes.size()]; - int i = 0; - for (String volume : volumes) { - result[i++] = volume; + public Parcelable[] getVolumeList() { + synchronized(mVolumes) { + int size = mVolumes.size(); + Parcelable[] result = new Parcelable[size]; + for (int i = 0; i < size; i++) { + result[i] = mVolumes.get(i); } return result; }