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

@ -133777,9 +133777,97 @@
>
<parameter name="path" type="java.lang.String">
</parameter>
<parameter name="state" type="java.lang.String">
<parameter name="state" type="int">
</parameter>
</method>
<field name="ERROR_ALREADY_MOUNTED"
type="int"
transient="false"
volatile="false"
value="24"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="ERROR_COULD_NOT_MOUNT"
type="int"
transient="false"
volatile="false"
value="21"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="ERROR_COULD_NOT_UNMOUNT"
type="int"
transient="false"
volatile="false"
value="22"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="ERROR_INTERNAL"
type="int"
transient="false"
volatile="false"
value="20"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="ERROR_NOT_MOUNTED"
type="int"
transient="false"
volatile="false"
value="23"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="ERROR_PERMISSION_DENIED"
type="int"
transient="false"
volatile="false"
value="25"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="MOUNTED"
type="int"
transient="false"
volatile="false"
value="1"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="UNMOUNTED"
type="int"
transient="false"
volatile="false"
value="2"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
</class>
<class name="StorageManager"
extends="java.lang.Object"
@ -133814,8 +133902,6 @@
>
<parameter name="filename" type="java.lang.String">
</parameter>
<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
</exception>
</method>
<method name="mountObb"
return="boolean"

View File

@ -484,7 +484,7 @@ public interface IMountService extends IInterface {
* IObbActionListener to inform it of the terminal state of the
* call.
*/
public void mountObb(String filename, String key, IObbActionListener token)
public void mountObb(String filename, String key, IObbActionListener token, int nonce)
throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
@ -493,6 +493,7 @@ public interface IMountService extends IInterface {
_data.writeString(filename);
_data.writeString(key);
_data.writeStrongBinder((token != null ? token.asBinder() : null));
_data.writeInt(nonce);
mRemote.transact(Stub.TRANSACTION_mountObb, _data, _reply, 0);
_reply.readException();
} finally {
@ -508,8 +509,8 @@ public interface IMountService extends IInterface {
* IObbActionListener to inform it of the terminal state of the
* call.
*/
public void unmountObb(String filename, boolean force, IObbActionListener token)
throws RemoteException {
public void unmountObb(String filename, boolean force, IObbActionListener token,
int nonce) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
@ -517,6 +518,7 @@ public interface IMountService extends IInterface {
_data.writeString(filename);
_data.writeInt((force ? 1 : 0));
_data.writeStrongBinder((token != null ? token.asBinder() : null));
_data.writeInt(nonce);
mRemote.transact(Stub.TRANSACTION_unmountObb, _data, _reply, 0);
_reply.readException();
} finally {
@ -855,7 +857,9 @@ public interface IMountService extends IInterface {
key = data.readString();
IObbActionListener observer;
observer = IObbActionListener.Stub.asInterface(data.readStrongBinder());
mountObb(filename, key, observer);
int nonce;
nonce = data.readInt();
mountObb(filename, key, observer, nonce);
reply.writeNoException();
return true;
}
@ -867,7 +871,9 @@ public interface IMountService extends IInterface {
force = 0 != data.readInt();
IObbActionListener observer;
observer = IObbActionListener.Stub.asInterface(data.readStrongBinder());
unmountObb(filename, force, observer);
int nonce;
nonce = data.readInt();
unmountObb(filename, force, observer, nonce);
reply.writeNoException();
return true;
}
@ -979,7 +985,7 @@ public interface IMountService extends IInterface {
* MountService will call back to the supplied IObbActionListener to inform
* it of the terminal state of the call.
*/
public void mountObb(String filename, String key, IObbActionListener token)
public void mountObb(String filename, String key, IObbActionListener token, int nonce)
throws RemoteException;
/*
@ -1023,7 +1029,7 @@ public interface IMountService extends IInterface {
* MountService will call back to the supplied IObbActionListener to inform
* it of the terminal state of the call.
*/
public void unmountObb(String filename, boolean force, IObbActionListener token)
public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
throws RemoteException;
/*

View File

@ -69,9 +69,11 @@ public interface IObbActionListener extends IInterface {
data.enforceInterface(DESCRIPTOR);
String filename;
filename = data.readString();
String status;
status = data.readString();
this.onObbResult(filename, status);
int nonce;
nonce = data.readInt();
int status;
status = data.readInt();
this.onObbResult(filename, nonce, status);
reply.writeNoException();
return true;
}
@ -101,13 +103,15 @@ public interface IObbActionListener extends IInterface {
* on
* @param returnCode status of the operation
*/
public void onObbResult(String filename, String status) throws RemoteException {
public void onObbResult(String filename, int nonce, int status)
throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(filename);
_data.writeString(status);
_data.writeInt(nonce);
_data.writeInt(status);
mRemote.transact(Stub.TRANSACTION_onObbResult, _data, _reply, 0);
_reply.readException();
} finally {
@ -124,7 +128,8 @@ public interface IObbActionListener extends IInterface {
* Return from an OBB action result.
*
* @param filename the path to the OBB the operation was performed on
* @param returnCode status of the operation
* @param nonce identifier that is meaningful to the receiver
* @param status status code as defined in {@link OnObbStateChangeListener}
*/
public void onObbResult(String filename, String status) throws RemoteException;
public void onObbResult(String filename, int nonce, int status) throws RemoteException;
}

View File

@ -17,15 +17,69 @@
package android.os.storage;
/**
* Used for receiving notifications from {@link StorageManager}.
* Used for receiving notifications from {@link StorageManager} about OBB file
* states.
*/
public abstract class OnObbStateChangeListener {
/**
* The OBB container is now mounted and ready for use. Returned in status
* messages from calls made via {@link StorageManager}
*/
public static final int MOUNTED = 1;
/**
* The OBB container is now unmounted and not usable. Returned in status
* messages from calls made via {@link StorageManager}
*/
public static final int UNMOUNTED = 2;
/**
* There was an internal system error encountered while trying to mount the
* OBB. Returned in status messages from calls made via
* {@link StorageManager}
*/
public static final int ERROR_INTERNAL = 20;
/**
* The OBB could not be mounted by the system. Returned in status messages
* from calls made via {@link StorageManager}
*/
public static final int ERROR_COULD_NOT_MOUNT = 21;
/**
* The OBB could not be unmounted. This most likely indicates that a file is
* in use on the OBB. Returned in status messages from calls made via
* {@link StorageManager}
*/
public static final int ERROR_COULD_NOT_UNMOUNT = 22;
/**
* A call was made to unmount the OBB when it was not mounted. Returned in
* status messages from calls made via {@link StorageManager}
*/
public static final int ERROR_NOT_MOUNTED = 23;
/**
* The OBB has already been mounted. Returned in status messages from calls
* made via {@link StorageManager}
*/
public static final int ERROR_ALREADY_MOUNTED = 24;
/**
* The current application does not have permission to use this OBB because
* the OBB indicates it's owned by a different package or the key used to
* open it is incorrect. Returned in status messages from calls made via
* {@link StorageManager}
*/
public static final int ERROR_PERMISSION_DENIED = 25;
/**
* Called when an OBB has changed states.
*
* @param path path to the OBB file the state change has happened on
* @param state the current state of the OBB
*/
public void onObbStateChange(String path, String state) {
public void onObbStateChange(String path, int state) {
}
}

View File

@ -22,12 +22,13 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* StorageManager is the interface to the systems storage service. The storage
@ -69,7 +70,12 @@ public class StorageManager
/*
* List of our listeners
*/
private ArrayList<ListenerDelegate> mListeners = new ArrayList<ListenerDelegate>();
private List<ListenerDelegate> mListeners = new ArrayList<ListenerDelegate>();
/*
* Next available nonce
*/
final private AtomicInteger mNextNonce = new AtomicInteger(0);
private class MountServiceBinderListener extends IMountServiceListener.Stub {
public void onUsbMassStorageConnectionChanged(boolean available) {
@ -93,57 +99,38 @@ public class StorageManager
private final ObbActionListener mObbActionListener = new ObbActionListener();
private class ObbActionListener extends IObbActionListener.Stub {
private List<WeakReference<ObbListenerDelegate>> mListeners = new LinkedList<WeakReference<ObbListenerDelegate>>();
private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
@Override
public void onObbResult(String filename, String status) throws RemoteException {
public void onObbResult(String filename, int nonce, int status) throws RemoteException {
final ObbListenerDelegate delegate;
synchronized (mListeners) {
final Iterator<WeakReference<ObbListenerDelegate>> iter = mListeners.iterator();
while (iter.hasNext()) {
final WeakReference<ObbListenerDelegate> ref = iter.next();
final ObbListenerDelegate delegate = (ref == null) ? null : ref.get();
if (delegate == null) {
iter.remove();
continue;
}
delegate.sendObbStateChanged(filename, status);
delegate = mListeners.get(nonce);
if (delegate != null) {
mListeners.remove(nonce);
}
}
if (delegate != null) {
delegate.sendObbStateChanged(filename, status);
}
}
public void addListener(OnObbStateChangeListener listener) {
if (listener == null) {
return;
}
public int addListener(OnObbStateChangeListener listener) {
final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
synchronized (mListeners) {
final Iterator<WeakReference<ObbListenerDelegate>> iter = mListeners.iterator();
while (iter.hasNext()) {
final WeakReference<ObbListenerDelegate> ref = iter.next();
final ObbListenerDelegate delegate = (ref == null) ? null : ref.get();
if (delegate == null) {
iter.remove();
continue;
}
/*
* If we're already in the listeners, we don't need to be in
* there again.
*/
if (listener.equals(delegate.getListener())) {
return;
}
}
final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
mListeners.add(new WeakReference<ObbListenerDelegate>(delegate));
mListeners.put(delegate.nonce, delegate);
}
return delegate.nonce;
}
}
private int getNextNonce() {
return mNextNonce.getAndIncrement();
}
/**
* Private class containing sender and receiver code for StorageEvents.
*/
@ -151,7 +138,10 @@ public class StorageManager
private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
private final Handler mHandler;
private final int nonce;
ObbListenerDelegate(OnObbStateChangeListener listener) {
nonce = getNextNonce();
mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
mHandler = new Handler(mTgtLooper) {
@Override
@ -180,7 +170,7 @@ public class StorageManager
return mObbEventListenerRef.get();
}
void sendObbStateChanged(String path, String state) {
void sendObbStateChanged(String path, int state) {
ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state);
mHandler.sendMessage(e.getMessage());
}
@ -191,9 +181,10 @@ public class StorageManager
*/
private class ObbStateChangedStorageEvent extends StorageEvent {
public final String path;
public final String state;
public ObbStateChangedStorageEvent(String path, String state) {
public final int state;
public ObbStateChangedStorageEvent(String path, int state) {
super(EVENT_OBB_STATE_CHANGED);
this.path = path;
this.state = state;
@ -420,10 +411,8 @@ public class StorageManager
* <p>
* The OBB will remain mounted for as long as the StorageManager reference
* is held by the application. As soon as this reference is lost, the OBBs
* in use will be unmounted. The {@link OnObbStateChangeListener} registered with
* this call will receive all further OBB-related events until it goes out
* of scope. If the caller is not interested in whether the call succeeds,
* the <code>listener</code> may be specified as <code>null</code>.
* in use will be unmounted. The {@link OnObbStateChangeListener} registered
* with this call will receive the success or failure of this operation.
* <p>
* <em>Note:</em> you can only mount OBB files for which the OBB tag on the
* file matches a package ID that is owned by the calling program's UID.
@ -433,12 +422,21 @@ public class StorageManager
* @param filename the path to the OBB file
* @param key secret used to encrypt the OBB; may be <code>null</code> if no
* encryption was used on the OBB.
* @param listener will receive the success or failure of the operation
* @return whether the mount call was successfully queued or not
*/
public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
try {
mObbActionListener.addListener(listener);
mMountService.mountObb(filename, key, mObbActionListener);
final int nonce = mObbActionListener.addListener(listener);
mMountService.mountObb(filename, key, mObbActionListener, nonce);
return true;
} catch (RemoteException e) {
Log.e(TAG, "Failed to mount OBB", e);
@ -452,10 +450,8 @@ public class StorageManager
* <code>force</code> flag is true, it will kill any application needed to
* unmount the given OBB (even the calling application).
* <p>
* The {@link OnObbStateChangeListener} registered with this call will receive all
* further OBB-related events until it goes out of scope. If the caller is
* not interested in whether the call succeeded, the listener may be
* specified as <code>null</code>.
* The {@link OnObbStateChangeListener} registered with this call will
* receive the success or failure of this operation.
* <p>
* <em>Note:</em> you can only mount OBB files for which the OBB tag on the
* file matches a package ID that is owned by the calling program's UID.
@ -466,12 +462,21 @@ public class StorageManager
* @param filename path to the OBB file
* @param force whether to kill any programs using this in order to unmount
* it
* @param listener will receive the success or failure of the operation
* @return whether the unmount call was successfully queued or not
*/
public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
try {
mObbActionListener.addListener(listener);
mMountService.unmountObb(filename, force, mObbActionListener);
final int nonce = mObbActionListener.addListener(listener);
mMountService.unmountObb(filename, force, mObbActionListener, nonce);
return true;
} catch (RemoteException e) {
Log.e(TAG, "Failed to mount OBB", e);
@ -486,7 +491,11 @@ public class StorageManager
* @param filename path to OBB image
* @return true if OBB is mounted; false if not mounted or on error
*/
public boolean isObbMounted(String filename) throws IllegalArgumentException {
public boolean isObbMounted(String filename) {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
try {
return mMountService.isObbMounted(filename);
} catch (RemoteException e) {
@ -506,12 +515,14 @@ public class StorageManager
* not mounted or exception encountered trying to read status
*/
public String getMountedObbPath(String filename) {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
try {
return mMountService.getMountedObbPath(filename);
} catch (RemoteException e) {
Log.e(TAG, "Failed to find mounted path for OBB", e);
} catch (IllegalArgumentException e) {
Log.d(TAG, "Couldn't read OBB file", e);
}
return null;

View File

@ -21,7 +21,6 @@ import com.android.frameworks.coretests.R;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.os.Environment;
import android.os.FileUtils;
import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageManager;
@ -57,17 +56,15 @@ public class MountServiceTests extends AndroidTestCase {
}
}
private interface CompletableTask {
public boolean isDone();
}
private static class ObbObserver extends OnObbStateChangeListener {
private String path;
private static class ObbObserver extends OnObbStateChangeListener implements CompletableTask {
public String path;
public String state;
public int state = -1;
boolean done = false;
@Override
public void onObbStateChange(String path, String state) {
public void onObbStateChange(String path, int state) {
Log.d(TAG, "Received message. path=" + path + ", state=" + state);
synchronized (this) {
this.path = path;
this.state = state;
@ -76,32 +73,43 @@ public class MountServiceTests extends AndroidTestCase {
}
}
public String getPath() {
assertTrue("Expected ObbObserver to have received a state change.", done);
return path;
}
public int getState() {
assertTrue("Expected ObbObserver to have received a state change.", done);
return state;
}
public void reset() {
this.path = null;
this.state = null;
this.state = -1;
done = false;
}
public boolean isDone() {
return done;
}
}
private boolean waitForCompletion(CompletableTask task) {
long waitTime = 0;
synchronized (task) {
while (!task.isDone() && waitTime < MAX_WAIT_TIME) {
try {
task.wait(WAIT_TIME_INCR);
waitTime += WAIT_TIME_INCR;
} catch (InterruptedException e) {
Log.i(TAG, "Interrupted during sleep", e);
public boolean waitForCompletion() {
long waitTime = 0;
synchronized (this) {
while (!isDone() && waitTime < MAX_WAIT_TIME) {
try {
wait(WAIT_TIME_INCR);
waitTime += WAIT_TIME_INCR;
} catch (InterruptedException e) {
Log.i(TAG, "Interrupted during sleep", e);
}
}
}
}
return task.isDone();
return isDone();
}
}
private File getFilePath(String name) {
final File filesDir = mContext.getFilesDir();
final File outFile = new File(filesDir, name);
@ -128,23 +136,52 @@ public class MountServiceTests extends AndroidTestCase {
}
private void mountObb(StorageManager sm, final int resource, final File file,
String expectedState) {
int expectedState) {
copyRawToFile(resource, file);
ObbObserver observer = new ObbObserver();
final ObbObserver observer = new ObbObserver();
assertTrue("mountObb call on " + file.getPath() + " should succeed",
sm.mountObb(file.getPath(), null, observer));
assertTrue("Mount should have completed",
waitForCompletion(observer));
observer.waitForCompletion());
if (expectedState == OnObbStateChangeListener.MOUNTED) {
assertTrue("OBB should be mounted", sm.isObbMounted(file.getPath()));
}
assertEquals("Actual file and resolved file should be the same",
file.getPath(), observer.path);
file.getPath(), observer.getPath());
assertEquals(expectedState, observer.state);
assertEquals(expectedState, observer.getState());
}
private String checkMountedPath(StorageManager sm, File file) {
private ObbObserver mountObbWithoutWait(final StorageManager sm, final int resource,
final File file) {
copyRawToFile(resource, file);
final ObbObserver observer = new ObbObserver();
assertTrue("mountObb call on " + file.getPath() + " should succeed", sm.mountObb(file
.getPath(), null, observer));
return observer;
}
private void waitForObbActionCompletion(final StorageManager sm, final File file,
final ObbObserver observer, int expectedState, boolean checkPath) {
assertTrue("Mount should have completed", observer.waitForCompletion());
assertTrue("OBB should be mounted", sm.isObbMounted(file.getPath()));
if (checkPath) {
assertEquals("Actual file and resolved file should be the same", file.getPath(),
observer.getPath());
}
assertEquals(expectedState, observer.getState());
}
private String checkMountedPath(final StorageManager sm, final File file) {
final String mountPath = sm.getMountedObbPath(file.getPath());
assertStartsWith("Path should be in " + OBB_MOUNT_PREFIX,
OBB_MOUNT_PREFIX,
@ -152,13 +189,21 @@ public class MountServiceTests extends AndroidTestCase {
return mountPath;
}
private void unmountObb(StorageManager sm, final File outFile) {
ObbObserver observer = new ObbObserver();
private void unmountObb(final StorageManager sm, final File file, int expectedState) {
final ObbObserver observer = new ObbObserver();
assertTrue("unmountObb call on test1.obb should succeed",
sm.unmountObb(outFile.getPath(), false, observer));
sm.unmountObb(file.getPath(),
false, observer));
assertTrue("Unmount should have completed",
waitForCompletion(observer));
observer.waitForCompletion());
assertEquals(expectedState, observer.getState());
if (expectedState == OnObbStateChangeListener.UNMOUNTED) {
assertFalse("OBB should not be mounted", sm.isObbMounted(file.getPath()));
}
}
@LargeTest
@ -167,7 +212,9 @@ public class MountServiceTests extends AndroidTestCase {
final File outFile = getFilePath("test1.obb");
mountObb(sm, R.raw.test1, outFile, Environment.MEDIA_MOUNTED);
mountObb(sm, R.raw.test1, outFile, OnObbStateChangeListener.MOUNTED);
mountObb(sm, R.raw.test1, outFile, OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
final String mountPath = checkMountedPath(sm, outFile);
final File mountDir = new File(mountPath);
@ -175,7 +222,7 @@ public class MountServiceTests extends AndroidTestCase {
assertTrue("OBB mounted path should be a directory",
mountDir.isDirectory());
unmountObb(sm, outFile);
unmountObb(sm, outFile, OnObbStateChangeListener.UNMOUNTED);
}
@LargeTest
@ -184,7 +231,7 @@ public class MountServiceTests extends AndroidTestCase {
final File outFile = getFilePath("test1_nosig.obb");
mountObb(sm, R.raw.test1_nosig, outFile, Environment.MEDIA_BAD_REMOVAL);
mountObb(sm, R.raw.test1_nosig, outFile, OnObbStateChangeListener.ERROR_INTERNAL);
assertFalse("OBB should not be mounted",
sm.isObbMounted(outFile.getPath()));
@ -199,7 +246,8 @@ public class MountServiceTests extends AndroidTestCase {
final File outFile = getFilePath("test1_wrongpackage.obb");
mountObb(sm, R.raw.test1_wrongpackage, outFile, Environment.MEDIA_BAD_REMOVAL);
mountObb(sm, R.raw.test1_wrongpackage, outFile,
OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
assertFalse("OBB should not be mounted",
sm.isObbMounted(outFile.getPath()));
@ -207,4 +255,31 @@ public class MountServiceTests extends AndroidTestCase {
assertNull("OBB's mounted path should be null",
sm.getMountedObbPath(outFile.getPath()));
}
@LargeTest
public void testMountAndUnmountTwoObbs() {
StorageManager sm = getStorageManager();
final File file1 = getFilePath("test1.obb");
final File file2 = getFilePath("test2.obb");
ObbObserver oo1 = mountObbWithoutWait(sm, R.raw.test1, file1);
ObbObserver oo2 = mountObbWithoutWait(sm, R.raw.test1, file2);
Log.d(TAG, "Waiting for OBB #1 to complete mount");
waitForObbActionCompletion(sm, file1, oo1, OnObbStateChangeListener.MOUNTED, false);
Log.d(TAG, "Waiting for OBB #2 to complete mount");
waitForObbActionCompletion(sm, file2, oo2, OnObbStateChangeListener.MOUNTED, false);
final String mountPath1 = checkMountedPath(sm, file1);
final File mountDir1 = new File(mountPath1);
assertTrue("OBB mounted path should be a directory", mountDir1.isDirectory());
final String mountPath2 = checkMountedPath(sm, file2);
final File mountDir2 = new File(mountPath2);
assertTrue("OBB mounted path should be a directory", mountDir2.isDirectory());
unmountObb(sm, file1, OnObbStateChangeListener.UNMOUNTED);
unmountObb(sm, file2, OnObbStateChangeListener.UNMOUNTED);
}
}

View File

@ -61,9 +61,9 @@ public:
virtual void shutdown(const sp<IMountShutdownObserver>& observer) = 0;
virtual void finishMediaUpdate() = 0;
virtual void mountObb(const String16& filename, const String16& key,
const sp<IObbActionListener>& token) = 0;
const sp<IObbActionListener>& token, const int32_t nonce) = 0;
virtual void unmountObb(const String16& filename, const bool force,
const sp<IObbActionListener>& token) = 0;
const sp<IObbActionListener>& token, const int32_t nonce) = 0;
virtual bool isObbMounted(const String16& filename) = 0;
virtual bool getMountedObbPath(const String16& filename, String16& path) = 0;
};

View File

@ -29,7 +29,7 @@ class IObbActionListener: public IInterface
public:
DECLARE_META_INTERFACE(ObbActionListener);
virtual void onObbResult(const String16& filename, const String16& status) = 0;
virtual void onObbResult(const String16& filename, const int32_t nonce, const int32_t state) = 0;
};
// ----------------------------------------------------------------------------

View File

@ -430,13 +430,14 @@ public:
}
void mountObb(const String16& filename, const String16& key,
const sp<IObbActionListener>& token)
const sp<IObbActionListener>& token, int32_t nonce)
{
Parcel data, reply;
data.writeInterfaceToken(IMountService::getInterfaceDescriptor());
data.writeString16(filename);
data.writeString16(key);
data.writeStrongBinder(token->asBinder());
data.writeInt32(nonce);
if (remote()->transact(TRANSACTION_mountObb, data, &reply) != NO_ERROR) {
LOGD("mountObb could not contact remote\n");
return;
@ -448,7 +449,7 @@ public:
}
}
void unmountObb(const String16& filename, const bool force, const sp<IObbActionListener>& token)
void unmountObb(const String16& filename, const bool force)
{
Parcel data, reply;
data.writeInterfaceToken(IMountService::getInterfaceDescriptor());

View File

@ -30,7 +30,7 @@ public:
: BpInterface<IObbActionListener>(impl)
{ }
virtual void onObbResult(const String16& filename, const String16& status) { }
virtual void onObbResult(const String16& filename, const int32_t nonce, const int32_t state) { }
};
IMPLEMENT_META_INTERFACE(ObbActionListener, "IObbActionListener");
@ -44,8 +44,9 @@ status_t BnObbActionListener::onTransact(
case TRANSACTION_onObbResult: {
CHECK_INTERFACE(IObbActionListener, data, reply);
String16 filename = data.readString16();
String16 state = data.readString16();
onObbResult(filename, state);
int32_t nonce = data.readInt32();
int32_t state = data.readInt32();
onObbResult(filename, nonce, state);
reply->writeNoException();
return NO_ERROR;
} break;

View File

@ -21,10 +21,13 @@
#include <binder/Binder.h>
#include <binder/IServiceManager.h>
#include <utils/Atomic.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <utils/String16.h>
#include <utils/Vector.h>
#include <utils/threads.h>
using namespace android;
@ -38,20 +41,46 @@ public:
mStorageManager(mgr)
{}
virtual void onObbResult(const android::String16& filename, const android::String16& state);
virtual void onObbResult(const android::String16& filename, const int32_t nonce,
const int32_t state);
};
class ObbCallback {
public:
ObbCallback(int32_t _nonce, AStorageManager_obbCallbackFunc _cb, void* _data)
: nonce(_nonce)
, cb(_cb)
, data(_data)
{}
int32_t nonce;
AStorageManager_obbCallbackFunc cb;
void* data;
};
struct AStorageManager : public RefBase {
protected:
AStorageManager_obbCallbackFunc mObbCallback;
void* mObbCallbackData;
Mutex mCallbackLock;
Vector<ObbCallback*> mCallbacks;
volatile int32_t mNextNonce;
sp<ObbActionListener> mObbActionListener;
sp<IMountService> mMountService;
int32_t getNextNonce() {
return android_atomic_inc(&mNextNonce);
}
ObbCallback* registerObbCallback(AStorageManager_obbCallbackFunc func, void* data) {
ObbCallback* cb = new ObbCallback(getNextNonce(), func, data);
{
AutoMutex _l(mCallbackLock);
mCallbacks.push(cb);
}
return cb;
}
public:
AStorageManager()
: mObbCallback(NULL)
, mObbCallbackData(NULL)
{
}
@ -73,26 +102,40 @@ public:
return true;
}
void setObbCallback(AStorageManager_obbCallbackFunc cb, void* data) {
mObbCallback = cb;
mObbCallbackData = data;
}
void fireCallback(const char* filename, const int32_t nonce, const int32_t state) {
ObbCallback* target = NULL;
{
AutoMutex _l(mCallbackLock);
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
ObbCallback* cb = mCallbacks.editItemAt(i);
if (cb->nonce == nonce) {
target = cb;
mCallbacks.removeAt(i);
break;
}
}
}
void fireCallback(const char* filename, const char* state) {
if (mObbCallback != NULL) {
mObbCallback(filename, state, mObbCallbackData);
if (target != NULL) {
target->cb(filename, state, target->data);
delete target;
} else {
LOGI("Didn't find the callback handler for: %s\n", filename);
}
}
void mountObb(const char* filename, const char* key) {
void mountObb(const char* filename, const char* key, AStorageManager_obbCallbackFunc func, void* data) {
ObbCallback* cb = registerObbCallback(func, data);
String16 filename16(filename);
String16 key16(key);
mMountService->mountObb(filename16, key16, mObbActionListener);
mMountService->mountObb(filename16, key16, mObbActionListener, cb->nonce);
}
void unmountObb(const char* filename, const bool force) {
void unmountObb(const char* filename, const bool force, AStorageManager_obbCallbackFunc func, void* data) {
ObbCallback* cb = registerObbCallback(func, data);
String16 filename16(filename);
mMountService->unmountObb(filename16, force, mObbActionListener);
mMountService->unmountObb(filename16, force, mObbActionListener, cb->nonce);
}
int isObbMounted(const char* filename) {
@ -111,8 +154,8 @@ public:
}
};
void ObbActionListener::onObbResult(const android::String16& filename, const android::String16& state) {
mStorageManager->fireCallback(String8(filename).string(), String8(state).string());
void ObbActionListener::onObbResult(const android::String16& filename, const int32_t nonce, const int32_t state) {
mStorageManager->fireCallback(String8(filename).string(), nonce, state);
}
@ -131,16 +174,14 @@ void AStorageManager_delete(AStorageManager* mgr) {
}
}
void AStorageManager_setObbCallback(AStorageManager* mgr, AStorageManager_obbCallbackFunc cb, void* data) {
mgr->setObbCallback(cb, data);
void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key,
AStorageManager_obbCallbackFunc cb, void* data) {
mgr->mountObb(filename, key, cb, data);
}
void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key) {
mgr->mountObb(filename, key);
}
void AStorageManager_unmountObb(AStorageManager* mgr, const char* filename, const int force) {
mgr->unmountObb(filename, force != 0);
void AStorageManager_unmountObb(AStorageManager* mgr, const char* filename, const int force,
AStorageManager_obbCallbackFunc cb, void* data) {
mgr->unmountObb(filename, force != 0, cb, data);
}
int AStorageManager_isObbMounted(AStorageManager* mgr, const char* filename) {

View File

@ -18,6 +18,8 @@
#ifndef ANDROID_STORAGE_MANAGER_H
#define ANDROID_STORAGE_MANAGER_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
@ -25,6 +27,60 @@ extern "C" {
struct AStorageManager;
typedef struct AStorageManager AStorageManager;
enum {
/*
* The OBB container is now mounted and ready for use. Can be returned
* as the status for callbacks made during asynchronous OBB actions.
*/
AOBB_STATE_MOUNTED = 1,
/*
* The OBB container is now unmounted and not usable. Can be returned
* as the status for callbacks made during asynchronous OBB actions.
*/
AOBB_STATE_UNMOUNTED = 2,
/*
* There was an internal system error encountered while trying to
* mount the OBB. Can be returned as the status for callbacks made
* during asynchronous OBB actions.
*/
AOBB_STATE_ERROR_INTERNAL = 20,
/*
* The OBB could not be mounted by the system. Can be returned as the
* status for callbacks made during asynchronous OBB actions.
*/
AOBB_STATE_ERROR_COULD_NOT_MOUNT = 21,
/*
* The OBB could not be unmounted. This most likely indicates that a
* file is in use on the OBB. Can be returned as the status for
* callbacks made during asynchronous OBB actions.
*/
AOBB_STATE_ERROR_COULD_NOT_UNMOUNT = 22,
/*
* A call was made to unmount the OBB when it was not mounted. Can be
* returned as the status for callbacks made during asynchronous OBB
* actions.
*/
AOBB_STATE_ERROR_NOT_MOUNTED = 23,
/*
* The OBB has already been mounted. Can be returned as the status for
* callbacks made during asynchronous OBB actions.
*/
AOBB_STATE_ERROR_ALREADY_MOUNTED = 24,
/*
* The current application does not have permission to use this OBB
* because the OBB indicates it's owned by a different package or the
* key used to open it is incorrect. Can be returned as the status for
* callbacks made during asynchronous OBB actions.
*/
AOBB_STATE_ERROR_PERMISSION_DENIED = 25,
};
/**
* Obtains a new instance of AStorageManager.
@ -39,22 +95,19 @@ void AStorageManager_delete(AStorageManager* mgr);
/**
* Callback function for asynchronous calls made on OBB files.
*/
typedef void (*AStorageManager_obbCallbackFunc)(const char* filename, const char* state, void* data);
/**
* Callback to call when requested asynchronous OBB operation is complete.
*/
void AStorageManager_setObbCallback(AStorageManager* mgr, AStorageManager_obbCallbackFunc cb, void* data);
typedef void (*AStorageManager_obbCallbackFunc)(const char* filename, const int32_t state, void* data);
/**
* Attempts to mount an OBB file. This is an asynchronous operation.
*/
void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key);
void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key,
AStorageManager_obbCallbackFunc cb, void* data);
/**
* Attempts to unmount an OBB file. This is an asynchronous operation.
*/
void AStorageManager_unmountObb(AStorageManager* mgr, const char* filename, const int force);
void AStorageManager_unmountObb(AStorageManager* mgr, const char* filename, const int force,
AStorageManager_obbCallbackFunc cb, void* data);
/**
* Check whether an OBB is mounted.

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