am 0a93d875: Merge "StorageManager: Clean up and generalize storage configuration resources" into honeycomb-mr2

* commit '0a93d875527c52183080b63a0b19d567856952af':
  StorageManager:  Clean up and generalize storage configuration resources
This commit is contained in:
Mike Lockwood
2011-05-16 12:36:29 -07:00
committed by Android Git Automerger
11 changed files with 389 additions and 90 deletions

View File

@@ -20,6 +20,7 @@ import java.io.File;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.storage.IMountService; import android.os.storage.IMountService;
import android.os.storage.StorageVolume;
import android.util.Log; import android.util.Log;
/** /**
@@ -35,7 +36,25 @@ public class Environment {
private static final Object mLock = new Object(); 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. * Gets the Android root directory.
@@ -416,9 +435,8 @@ public class Environment {
* <p>See {@link #getExternalStorageDirectory()} for more information. * <p>See {@link #getExternalStorageDirectory()} for more information.
*/ */
public static boolean isExternalStorageRemovable() { public static boolean isExternalStorageRemovable() {
if (isExternalStorageEmulated()) return false; StorageVolume volume = getPrimaryVolume();
return Resources.getSystem().getBoolean( return (volume != null && volume.isRemovable());
com.android.internal.R.bool.config_externalStorageRemovable);
} }
/** /**
@@ -435,23 +453,8 @@ public class Environment {
* android.content.ComponentName, boolean)} for additional details. * android.content.ComponentName, boolean)} for additional details.
*/ */
public static boolean isExternalStorageEmulated() { public static boolean isExternalStorageEmulated() {
if (mIsExternalStorageEmulated == null) { StorageVolume volume = getPrimaryVolume();
synchronized (mLock) { return (volume != null && volume.isEmulated());
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;
} }
static File getDirectory(String variableName, String defaultPath) { static File getDirectory(String variableName, String defaultPath) {

View File

@@ -20,7 +20,9 @@ import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.os.IInterface; import android.os.IInterface;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.storage.StorageVolume;
/** /**
* WARNING! Update IMountService.h and IMountService.cpp if you change this * WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -638,15 +640,15 @@ public interface IMountService extends IInterface {
return _result; return _result;
} }
public String[] getVolumeList() throws RemoteException { public Parcelable[] getVolumeList() throws RemoteException {
Parcel _data = Parcel.obtain(); Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain(); Parcel _reply = Parcel.obtain();
String[] _result; Parcelable[] _result;
try { try {
_data.writeInterfaceToken(DESCRIPTOR); _data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0); mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
_reply.readException(); _reply.readException();
_result = _reply.readStringArray(); _result = _reply.readParcelableArray(StorageVolume.class.getClassLoader());
} finally { } finally {
_reply.recycle(); _reply.recycle();
_data.recycle(); _data.recycle();
@@ -1024,9 +1026,9 @@ public interface IMountService extends IInterface {
} }
case TRANSACTION_getVolumeList: { case TRANSACTION_getVolumeList: {
data.enforceInterface(DESCRIPTOR); data.enforceInterface(DESCRIPTOR);
String[] result = getVolumeList(); Parcelable[] result = getVolumeList();
reply.writeNoException(); reply.writeNoException();
reply.writeStringArray(result); reply.writeParcelableArray(result, 0);
return true; return true;
} }
} }
@@ -1207,5 +1209,5 @@ public interface IMountService extends IInterface {
/** /**
* Returns list of all mountable volumes. * Returns list of all mountable volumes.
*/ */
public String[] getVolumeList() throws RemoteException; public Parcelable[] getVolumeList() throws RemoteException;
} }

View File

@@ -19,6 +19,7 @@ package android.os.storage;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.util.Log; import android.util.Log;
@@ -545,12 +546,34 @@ public class StorageManager
* Returns list of all mountable volumes. * Returns list of all mountable volumes.
* @hide * @hide
*/ */
public String[] getVolumeList() { public StorageVolume[] getVolumeList() {
try { 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) { } catch (RemoteException e) {
Log.e(TAG, "Failed to get volume list", e); Log.e(TAG, "Failed to get volume list", e);
return null; 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;
}
} }

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -4878,4 +4878,21 @@
<!-- Y coordinate of the icon hot spot. --> <!-- Y coordinate of the icon hot spot. -->
<attr name="hotSpotY" format="float" /> <attr name="hotSpotY" format="float" />
</declare-styleable> </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> </resources>

View File

@@ -94,52 +94,10 @@
when there's no network connection. If the scan doesn't timeout, use zero --> when there's no network connection. If the scan doesn't timeout, use zero -->
<integer name="config_radioScanningTimeout">0</integer> <integer name="config_radioScanningTimeout">0</integer>
<!-- Set to true if the location returned Environment.getExternalStorageDirectory() <!-- Storage lists for default and nosdcard products.
is actually a subdirectory of the internal storage. Both have a single SD card storage, but the storage is not removable in the nosdcard case -->
If this is set then Environment.getExternalStorageState() will always return <integer name="config_storageListId" product="nosdcard">@xml/storage_list_nosdcard</integer>
MEDIA_MOUNTED and Intent.ACTION_MEDIA_MOUNTED will be broadcast at boot time <integer name="config_storageListId" product="default">@xml/storage_list</integer>
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>
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION. <!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. --> Please don't copy them, copy anything else. -->

View File

@@ -2808,4 +2808,12 @@
<!-- Description of the button to decrement the NumberPicker value. [CHAR LIMIT=NONE] --> <!-- Description of the button to decrement the NumberPicker value. [CHAR LIMIT=NONE] -->
<string name="number_picker_decrement_button">Decrement</string> <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> </resources>

View File

@@ -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>

View File

@@ -1147,8 +1147,7 @@ public class MediaScanner
mGenresUri = Genres.getContentUri(volumeName); mGenresUri = Genres.getContentUri(volumeName);
mPlaylistsUri = Playlists.getContentUri(volumeName); mPlaylistsUri = Playlists.getContentUri(volumeName);
mCaseInsensitivePaths = !mContext.getResources().getBoolean( mCaseInsensitivePaths = true;
com.android.internal.R.bool.config_caseSensitiveExternalStorage);
if (!Process.supportsProcesses()) { if (!Process.supportsProcesses()) {
// Simulator uses host file system, so it should be case sensitive. // Simulator uses host file system, so it should be case sensitive.
mCaseInsensitivePaths = false; mCaseInsensitivePaths = false;

View File

@@ -17,6 +17,7 @@
package com.android.server; package com.android.server;
import com.android.internal.app.IMediaContainerService; import com.android.internal.app.IMediaContainerService;
import com.android.internal.util.XmlUtils;
import com.android.server.am.ActivityManagerService; import com.android.server.am.ActivityManagerService;
import android.Manifest; import android.Manifest;
@@ -28,6 +29,9 @@ import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.ObbInfo; 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.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.Environment; import android.os.Environment;
@@ -36,6 +40,7 @@ import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.SystemClock; import android.os.SystemClock;
@@ -46,8 +51,14 @@ import android.os.storage.IMountShutdownObserver;
import android.os.storage.IObbActionListener; import android.os.storage.IObbActionListener;
import android.os.storage.OnObbStateChangeListener; import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageResultCode; import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Slog; 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.FileDescriptor;
import java.io.IOException; import java.io.IOException;
@@ -145,6 +156,8 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC
private Context mContext; private Context mContext;
private NativeDaemonConnector mConnector; private NativeDaemonConnector mConnector;
private final ArrayList<StorageVolume> mVolumes = new ArrayList<StorageVolume>();
private StorageVolume mPrimaryVolume;
private final HashMap<String, String> mVolumeStates = new HashMap<String, String>(); private final HashMap<String, String> mVolumeStates = new HashMap<String, String>();
private String mExternalStoragePath; private String mExternalStoragePath;
private PackageManagerService mPms; 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 * Constructs a new MountService instance
* *
@@ -1075,13 +1156,16 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC
*/ */
public MountService(Context context) { public MountService(Context context) {
mContext = context; mContext = context;
Resources resources = context.getResources();
readStorageList(resources);
mExternalStoragePath = Environment.getExternalStorageDirectory().getPath(); if (mPrimaryVolume != null) {
mEmulateExternalStorage = context.getResources().getBoolean( mExternalStoragePath = mPrimaryVolume.getPath();
com.android.internal.R.bool.config_emulateExternalStorage); mEmulateExternalStorage = mPrimaryVolume.isEmulated();
if (mEmulateExternalStorage) { if (mEmulateExternalStorage) {
Slog.d(TAG, "using emulated external storage"); Slog.d(TAG, "using emulated external storage");
mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED); mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED);
}
} }
// XXX: This will go away soon in favor of IMountServiceObserver // XXX: This will go away soon in favor of IMountServiceObserver
@@ -1753,13 +1837,12 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC
} }
} }
public String[] getVolumeList() { public Parcelable[] getVolumeList() {
synchronized(mVolumeStates) { synchronized(mVolumes) {
Set<String> volumes = mVolumeStates.keySet(); int size = mVolumes.size();
String[] result = new String[volumes.size()]; Parcelable[] result = new Parcelable[size];
int i = 0; for (int i = 0; i < size; i++) {
for (String volume : volumes) { result[i] = mVolumes.get(i);
result[i++] = volume;
} }
return result; return result;
} }