/** Intent used to bind to the service */
private final Intent mIntent;
+ private static final int STATE_IDLE = 0; // no bind operation is ongoing
+ private static final int STATE_BINDING = 1; // someone is binding and waiting
+ private static final int STATE_PENDING = 2; // a bind is pending, but the caller is not waiting
+
@GuardedBy("mLock")
- private volatile boolean mIsBinding;
+ private int mBindState = STATE_IDLE;
@GuardedBy("mLock")
private IInstantAppResolver mRemoteInstance;
private IInstantAppResolver getRemoteInstanceLazy(String token)
throws ConnectionException, TimeoutException, InterruptedException {
- synchronized (mLock) {
- if (mRemoteInstance != null) {
- return mRemoteInstance;
- }
- long binderToken = Binder.clearCallingIdentity();
- try {
- bindLocked(token);
- } finally {
- Binder.restoreCallingIdentity(binderToken);
- }
- return mRemoteInstance;
+ long binderToken = Binder.clearCallingIdentity();
+ try {
+ return bind(token);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
}
}
private void waitForBindLocked(String token) throws TimeoutException, InterruptedException {
final long startMillis = SystemClock.uptimeMillis();
- while (mIsBinding) {
+ while (mBindState != STATE_IDLE) {
if (mRemoteInstance != null) {
break;
}
}
}
- private void bindLocked(String token)
+ private IInstantAppResolver bind(String token)
throws ConnectionException, TimeoutException, InterruptedException {
- if (DEBUG_EPHEMERAL && mIsBinding && mRemoteInstance == null) {
- Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection");
- }
- try {
- waitForBindLocked(token);
- } catch (TimeoutException e) {
- if (DEBUG_EPHEMERAL) {
- Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding");
+ boolean doUnbind = false;
+ synchronized (mLock) {
+ if (mRemoteInstance != null) {
+ return mRemoteInstance;
}
- mContext.unbindService(mServiceConnection);
- }
- if (mRemoteInstance != null) {
- return;
- }
- mIsBinding = true;
- if (DEBUG_EPHEMERAL) {
- Slog.v(TAG, "[" + token + "] Binding to instant app resolver");
+
+ if (mBindState == STATE_PENDING) {
+ // there is a pending bind, let's see if we can use it.
+ if (DEBUG_EPHEMERAL) {
+ Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection");
+ }
+ try {
+ waitForBindLocked(token);
+ if (mRemoteInstance != null) {
+ return mRemoteInstance;
+ }
+ } catch (TimeoutException e) {
+ // nope, we might have to try a rebind.
+ doUnbind = true;
+ }
+ }
+
+ if (mBindState == STATE_BINDING) {
+ // someone was binding when we called bind(), or they raced ahead while we were
+ // waiting in the PENDING case; wait for their result instead. Last chance!
+ if (DEBUG_EPHEMERAL) {
+ Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection");
+ }
+ waitForBindLocked(token);
+ // if the other thread's bindService() returned false, we could still have null.
+ if (mRemoteInstance != null) {
+ return mRemoteInstance;
+ }
+ throw new ConnectionException(ConnectionException.FAILURE_BIND);
+ }
+ mBindState = STATE_BINDING; // our time to shine! :)
}
+
+ // only one thread can be here at a time (the one that set STATE_BINDING)
boolean wasBound = false;
+ IInstantAppResolver instance = null;
try {
+ if (doUnbind) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding");
+ }
+ mContext.unbindService(mServiceConnection);
+ }
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "[" + token + "] Binding to instant app resolver");
+ }
final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
wasBound = mContext
.bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM);
if (wasBound) {
- waitForBindLocked(token);
+ synchronized (mLock) {
+ waitForBindLocked(token);
+ instance = mRemoteInstance;
+ return instance;
+ }
} else {
Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent);
throw new ConnectionException(ConnectionException.FAILURE_BIND);
}
} finally {
- mIsBinding = wasBound && mRemoteInstance == null;
- mLock.notifyAll();
+ synchronized (mLock) {
+ if (wasBound && instance == null) {
+ mBindState = STATE_PENDING;
+ } else {
+ mBindState = STATE_IDLE;
+ }
+ mLock.notifyAll();
+ }
}
}
}
synchronized (mLock) {
mRemoteInstance = IInstantAppResolver.Stub.asInterface(service);
- mIsBinding = false;
+ if (mBindState == STATE_PENDING) {
+ mBindState = STATE_IDLE;
+ }
try {
service.linkToDeath(EphemeralResolverConnection.this, 0 /*flags*/);
} catch (RemoteException e) {