am b4de3dca: Merge "OBB: rearrange to be entirely asynchronous" into gingerbread

Merge commit 'b4de3dca96b9ff18562062e181dcd8b83e641e45' into gingerbread-plus-aosp

* commit 'b4de3dca96b9ff18562062e181dcd8b83e641e45':
  OBB: rearrange to be entirely asynchronous
This commit is contained in:
Kenny Root
2010-10-11 17:31:06 -07:00
committed by Android Git Automerger
13 changed files with 734 additions and 439 deletions

View File

@@ -44,6 +44,7 @@ import android.os.storage.IMountService;
import android.os.storage.IMountServiceListener;
import android.os.storage.IMountShutdownObserver;
import android.os.storage.IObbActionListener;
import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageResultCode;
import android.security.MessageDigest;
import android.util.Slog;
@@ -53,7 +54,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -161,25 +161,25 @@ class MountService extends IMountService.Stub
final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
class ObbState implements IBinder.DeathRecipient {
public ObbState(String filename, IObbActionListener token, int callerUid)
public ObbState(String filename, int callerUid, IObbActionListener token, int nonce)
throws RemoteException {
this.filename = filename;
this.token = token;
this.callerUid = callerUid;
mounted = false;
this.token = token;
this.nonce = nonce;
}
// OBB source filename
final String filename;
// Token of remote Binder caller
final IObbActionListener token;
String filename;
// Binder.callingUid()
final public int callerUid;
// Whether this is mounted currently.
boolean mounted;
// Token of remote Binder caller
final IObbActionListener token;
// Identifier to pass back to the token
final int nonce;
public IBinder getBinder() {
return token.asBinder();
@@ -208,8 +208,6 @@ class MountService extends IMountService.Stub
sb.append(token.toString());
sb.append(",callerUid=");
sb.append(callerUid);
sb.append(",mounted=");
sb.append(mounted);
sb.append('}');
return sb.toString();
}
@@ -223,6 +221,7 @@ class MountService extends IMountService.Stub
private static final int OBB_MCS_BOUND = 2;
private static final int OBB_MCS_UNBIND = 3;
private static final int OBB_MCS_RECONNECT = 4;
private static final int OBB_FLUSH_MOUNT_STATE = 5;
/*
* Default Container Service information
@@ -500,40 +499,23 @@ class MountService extends IMountService.Stub
Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
return;
}
// Update state on PackageManager
if (Environment.MEDIA_UNMOUNTED.equals(state)) {
// Tell the package manager the media is gone.
mPms.updateExternalMediaStatus(false, false);
/*
* Some OBBs might have been unmounted when this volume was
* unmounted, so send a message to the handler to let it know to
* remove those from the list of mounted OBBS.
*/
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,
path));
} else if (Environment.MEDIA_MOUNTED.equals(state)) {
// Tell the package manager the media is available for use.
mPms.updateExternalMediaStatus(true, false);
}
// Remove all OBB mappings and listeners from this path
synchronized (mObbMounts) {
final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
final Iterator<Entry<String, ObbState>> i = mObbPathToStateMap.entrySet().iterator();
while (i.hasNext()) {
final Entry<String, ObbState> obbEntry = i.next();
// If this entry's source file is in the volume path that got
// unmounted, remove it because it's no longer valid.
if (obbEntry.getKey().startsWith(path)) {
obbStatesToRemove.add(obbEntry.getValue());
}
}
for (final ObbState obbState : obbStatesToRemove) {
removeObbState(obbState);
try {
obbState.token.onObbResult(obbState.filename, Environment.MEDIA_UNMOUNTED);
} catch (RemoteException e) {
Slog.i(TAG, "Couldn't send unmount notification for OBB: "
+ obbState.filename);
}
}
}
String oldState = mLegacyState;
mLegacyState = state;
@@ -1530,6 +1512,10 @@ class MountService extends IMountService.Stub
}
public String getMountedObbPath(String filename) {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
waitForReady();
warnOnNotMounted();
@@ -1552,164 +1538,98 @@ class MountService extends IMountService.Stub
}
public boolean isObbMounted(String filename) {
synchronized (mObbMounts) {
final ObbState obbState = mObbPathToStateMap.get(filename);
if (obbState != null) {
synchronized (obbState) {
return obbState.mounted;
}
}
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
synchronized (mObbMounts) {
return mObbPathToStateMap.containsKey(filename);
}
return false;
}
public void mountObb(String filename, String key, IObbActionListener token)
public void mountObb(String filename, String key, IObbActionListener token, int nonce)
throws RemoteException {
waitForReady();
warnOnNotMounted();
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
} else if (token == null) {
}
if (token == null) {
throw new IllegalArgumentException("token cannot be null");
}
final ObbState obbState;
synchronized (mObbMounts) {
if (isObbMounted(filename)) {
try {
token.onObbResult(filename, Environment.MEDIA_MOUNTED);
} catch (RemoteException e) {
Slog.d(TAG, "Could not send unmount notification for: " + filename);
}
return;
}
final int callerUid = Binder.getCallingUid();
obbState = new ObbState(filename, token, callerUid);
addObbState(obbState);
}
String hashedKey = null;
if (key != null) {
final MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
Slog.e(TAG, "Could not load MD5 algorithm", e);
try {
token.onObbResult(filename, Environment.MEDIA_UNMOUNTED);
} catch (RemoteException e1) {
Slog.d(TAG, "Could not send unmount notification for: " + filename);
}
return;
}
hashedKey = HexDump.toHexString(md.digest(key.getBytes()));
}
ObbAction action = new MountObbAction(obbState, hashedKey);
final int callerUid = Binder.getCallingUid();
final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
final ObbAction action = new MountObbAction(obbState, key);
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
if (DEBUG_OBB)
Slog.i(TAG, "Send to OBB handler: " + action.toString());
}
public void unmountObb(String filename, boolean force, IObbActionListener token) {
public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
throws RemoteException {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
} else if (token == null) {
throw new IllegalArgumentException("token cannot be null");
}
final ObbState obbState;
synchronized (mObbMounts) {
if (!isObbMounted(filename)) {
try {
token.onObbResult(filename, Environment.MEDIA_UNMOUNTED);
} catch (RemoteException e) {
Slog.d(TAG, "Could not send unmount notification for: " + filename);
}
return;
}
obbState = mObbPathToStateMap.get(filename);
if (Binder.getCallingUid() != obbState.callerUid) {
throw new SecurityException("caller UID does not match original mount caller UID");
} else if (!token.asBinder().equals(obbState.getBinder())) {
throw new SecurityException("caller does not match original mount caller");
}
}
ObbAction action = new UnmountObbAction(obbState, force);
final int callerUid = Binder.getCallingUid();
final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
final ObbAction action = new UnmountObbAction(obbState, force);
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
if (DEBUG_OBB)
Slog.i(TAG, "Send to OBB handler: " + action.toString());
}
private void addObbState(ObbState obbState) throws RemoteException {
synchronized (mObbMounts) {
final IBinder binder = obbState.getBinder();
List<ObbState> obbStates = mObbMounts.get(binder);
final boolean unique;
private void addObbStateLocked(ObbState obbState) throws RemoteException {
final IBinder binder = obbState.getBinder();
List<ObbState> obbStates = mObbMounts.get(binder);
if (obbStates == null) {
obbStates = new ArrayList<ObbState>();
mObbMounts.put(binder, obbStates);
unique = true;
} else {
unique = obbStates.contains(obbState);
}
if (unique) {
obbStates.add(obbState);
try {
obbState.link();
} catch (RemoteException e) {
/*
* The binder died before we could link it, so clean up our
* state and return failure.
*/
obbStates.remove(obbState);
if (obbStates.isEmpty()) {
mObbMounts.remove(binder);
}
// Rethrow the error so mountObb can get it
throw e;
if (obbStates == null) {
obbStates = new ArrayList<ObbState>();
mObbMounts.put(binder, obbStates);
} else {
for (final ObbState o : obbStates) {
if (o.filename.equals(obbState.filename)) {
throw new IllegalStateException("Attempt to add ObbState twice. "
+ "This indicates an error in the MountService logic.");
}
}
mObbPathToStateMap.put(obbState.filename, obbState);
}
obbStates.add(obbState);
try {
obbState.link();
} catch (RemoteException e) {
/*
* The binder died before we could link it, so clean up our state
* and return failure.
*/
obbStates.remove(obbState);
if (obbStates.isEmpty()) {
mObbMounts.remove(binder);
}
// Rethrow the error so mountObb can get it
throw e;
}
mObbPathToStateMap.put(obbState.filename, obbState);
}
private void removeObbState(ObbState obbState) {
synchronized (mObbMounts) {
final IBinder binder = obbState.getBinder();
final List<ObbState> obbStates = mObbMounts.get(binder);
if (obbStates != null) {
if (obbStates.remove(obbState)) {
obbState.unlink();
}
if (obbStates.isEmpty()) {
mObbMounts.remove(binder);
}
private void removeObbStateLocked(ObbState obbState) {
final IBinder binder = obbState.getBinder();
final List<ObbState> obbStates = mObbMounts.get(binder);
if (obbStates != null) {
if (obbStates.remove(obbState)) {
obbState.unlink();
}
if (obbStates.isEmpty()) {
mObbMounts.remove(binder);
}
mObbPathToStateMap.remove(obbState.filename);
}
}
private void replaceObbState(ObbState oldObbState, ObbState newObbState) throws RemoteException {
synchronized (mObbMounts) {
removeObbState(oldObbState);
addObbState(newObbState);
}
mObbPathToStateMap.remove(obbState.filename);
}
private class ObbActionHandler extends Handler {
@@ -1808,6 +1728,47 @@ class MountService extends IMountService.Stub
}
break;
}
case OBB_FLUSH_MOUNT_STATE: {
final String path = (String) msg.obj;
if (DEBUG_OBB)
Slog.i(TAG, "Flushing all OBB state for path " + path);
synchronized (mObbMounts) {
final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
final Iterator<Entry<String, ObbState>> i =
mObbPathToStateMap.entrySet().iterator();
while (i.hasNext()) {
final Entry<String, ObbState> obbEntry = i.next();
/*
* If this entry's source file is in the volume path
* that got unmounted, remove it because it's no
* longer valid.
*/
if (obbEntry.getKey().startsWith(path)) {
obbStatesToRemove.add(obbEntry.getValue());
}
}
for (final ObbState obbState : obbStatesToRemove) {
if (DEBUG_OBB)
Slog.i(TAG, "Removing state for " + obbState.filename);
removeObbStateLocked(obbState);
try {
obbState.token.onObbResult(obbState.filename, obbState.nonce,
OnObbStateChangeListener.UNMOUNTED);
} catch (RemoteException e) {
Slog.i(TAG, "Couldn't send unmount notification for OBB: "
+ obbState.filename);
}
}
}
break;
}
}
}
@@ -1886,9 +1847,13 @@ class MountService extends IMountService.Stub
return obbInfo;
}
protected void sendNewStatusOrIgnore(String filename, String status) {
protected void sendNewStatusOrIgnore(int status) {
if (mObbState == null || mObbState.token == null) {
return;
}
try {
mObbState.token.onObbResult(filename, status);
mObbState.token.onObbResult(mObbState.filename, mObbState.nonce, status);
} catch (RemoteException e) {
Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
}
@@ -1904,89 +1869,80 @@ class MountService extends IMountService.Stub
}
public void handleExecute() throws IOException, RemoteException {
waitForReady();
warnOnNotMounted();
final ObbInfo obbInfo = getObbInfo();
/*
* If someone tried to trick us with some weird characters, rectify
* it here.
*/
if (!mObbState.filename.equals(obbInfo.filename)) {
if (DEBUG_OBB)
Slog.i(TAG, "OBB filename " + mObbState.filename + " is actually "
+ obbInfo.filename);
synchronized (mObbMounts) {
/*
* If the real filename is already mounted, discard this
* state and notify the caller that the OBB is already
* mounted.
*/
if (isObbMounted(obbInfo.filename)) {
if (DEBUG_OBB)
Slog.i(TAG, "OBB already mounted as " + obbInfo.filename);
removeObbState(mObbState);
sendNewStatusOrIgnore(obbInfo.filename, Environment.MEDIA_MOUNTED);
return;
}
/*
* It's not already mounted, so we have to replace the state
* with the state containing the actual filename.
*/
ObbState newObbState = new ObbState(obbInfo.filename, mObbState.token,
mObbState.callerUid);
replaceObbState(mObbState, newObbState);
mObbState = newObbState;
}
}
if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
throw new IllegalArgumentException("Caller package does not match OBB file");
Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
+ " which is owned by " + obbInfo.packageName);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
return;
}
boolean mounted = false;
int rc;
synchronized (mObbState) {
if (mObbState.mounted) {
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
final boolean isMounted;
synchronized (mObbMounts) {
isMounted = mObbPathToStateMap.containsKey(obbInfo.filename);
}
if (isMounted) {
Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
return;
}
/*
* The filename passed in might not be the canonical name, so just
* set the filename to the canonicalized version.
*/
mObbState.filename = obbInfo.filename;
final String hashedKey;
if (mKey == null) {
hashedKey = "none";
} else {
final MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
Slog.e(TAG, "Could not load MD5 algorithm", e);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
return;
}
rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("obb mount %s %s %d", mObbState.filename,
mKey != null ? mKey : "none",
mObbState.callerUid);
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
int code = e.getCode();
if (code != VoldResponseCode.OpFailedStorageBusy) {
rc = StorageResultCode.OperationFailedInternalError;
}
}
hashedKey = HexDump.toHexString(md.digest(mKey.getBytes()));
}
if (rc == StorageResultCode.OperationSucceeded) {
mObbState.mounted = mounted = true;
int rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("obb mount %s %s %d", mObbState.filename, hashedKey,
mObbState.callerUid);
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
int code = e.getCode();
if (code != VoldResponseCode.OpFailedStorageBusy) {
rc = StorageResultCode.OperationFailedInternalError;
}
}
if (mounted) {
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
if (rc == StorageResultCode.OperationSucceeded) {
if (DEBUG_OBB)
Slog.d(TAG, "Successfully mounted OBB " + mObbState.filename);
synchronized (mObbMounts) {
addObbStateLocked(mObbState);
}
sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED);
} else {
Slog.e(TAG, "Couldn't mount OBB file: " + rc);
// We didn't succeed, so remove this from the mount-set.
removeObbState(mObbState);
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
}
}
public void handleError() {
removeObbState(mObbState);
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
}
@Override
@@ -1999,6 +1955,8 @@ class MountService extends IMountService.Stub
sb.append(mObbState.callerUid);
sb.append(",token=");
sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL");
sb.append(",binder=");
sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
sb.append('}');
return sb.toString();
}
@@ -2013,68 +1971,61 @@ class MountService extends IMountService.Stub
}
public void handleExecute() throws IOException {
waitForReady();
warnOnNotMounted();
final ObbInfo obbInfo = getObbInfo();
/*
* If someone tried to trick us with some weird characters, rectify
* it here.
*/
final ObbState obbState;
synchronized (mObbMounts) {
if (!isObbMounted(obbInfo.filename)) {
removeObbState(mObbState);
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
return;
}
obbState = mObbPathToStateMap.get(obbInfo.filename);
}
if (!mObbState.filename.equals(obbInfo.filename)) {
removeObbState(mObbState);
mObbState = mObbPathToStateMap.get(obbInfo.filename);
if (obbState == null) {
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
return;
}
if (obbState.callerUid != mObbState.callerUid) {
Slog.w(TAG, "Permission denied attempting to unmount OBB " + obbInfo.filename
+ " (owned by " + obbInfo.packageName + ")");
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
return;
}
mObbState.filename = obbInfo.filename;
int rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("obb unmount %s%s", mObbState.filename,
(mForceUnmount ? " force" : ""));
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
int code = e.getCode();
if (code == VoldResponseCode.OpFailedStorageBusy) {
rc = StorageResultCode.OperationFailedStorageBusy;
} else if (code == VoldResponseCode.OpFailedStorageNotFound) {
// If it's not mounted then we've already won.
rc = StorageResultCode.OperationSucceeded;
} else {
rc = StorageResultCode.OperationFailedInternalError;
}
}
boolean unmounted = false;
synchronized (mObbState) {
if (!mObbState.mounted) {
sendNewStatusOrIgnore(obbInfo.filename, Environment.MEDIA_UNMOUNTED);
return;
if (rc == StorageResultCode.OperationSucceeded) {
synchronized (mObbMounts) {
removeObbStateLocked(obbState);
}
int rc = StorageResultCode.OperationSucceeded;
String cmd = String.format("obb unmount %s%s", mObbState.filename,
(mForceUnmount ? " force" : ""));
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
int code = e.getCode();
if (code == VoldResponseCode.OpFailedStorageBusy) {
rc = StorageResultCode.OperationFailedStorageBusy;
} else if (code == VoldResponseCode.OpFailedStorageNotFound) {
// If it's not mounted then we've already won.
rc = StorageResultCode.OperationSucceeded;
} else {
rc = StorageResultCode.OperationFailedInternalError;
}
}
if (rc == StorageResultCode.OperationSucceeded) {
mObbState.mounted = false;
unmounted = true;
}
}
if (unmounted) {
removeObbState(mObbState);
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
} else {
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
Slog.w(TAG, "Could not mount OBB: " + mObbState.filename);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
}
}
public void handleError() {
removeObbState(mObbState);
sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
}
@Override
@@ -2090,7 +2041,7 @@ class MountService extends IMountService.Stub
sb.append(",token=");
sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
sb.append(",binder=");
sb.append(mObbState.getBinder().toString());
sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
sb.append('}');
return sb.toString();
}
@@ -2105,16 +2056,27 @@ class MountService extends IMountService.Stub
return;
}
pw.println(" mObbMounts:");
synchronized (mObbMounts) {
final Collection<List<ObbState>> obbStateLists = mObbMounts.values();
pw.println(" mObbMounts:");
for (final List<ObbState> obbStates : obbStateLists) {
final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator();
while (binders.hasNext()) {
Entry<IBinder, List<ObbState>> e = binders.next();
pw.print(" Key="); pw.println(e.getKey().toString());
final List<ObbState> obbStates = e.getValue();
for (final ObbState obbState : obbStates) {
pw.print(" "); pw.println(obbState.toString());
pw.print(" "); pw.println(obbState.toString());
}
}
pw.println("");
pw.println(" mObbPathToStateMap:");
final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
while (maps.hasNext()) {
final Entry<String, ObbState> e = maps.next();
pw.print(" "); pw.print(e.getKey());
pw.print(" -> "); pw.println(e.getValue().toString());
}
}
}
}