Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a3735dc4 authored by Todd Kennedy's avatar Todd Kennedy Committed by Android (Google) Code Review
Browse files

Merge "Fix deadlock between AMS and EphemeralResolverConnection" into oc-mr1-dev

parents 9f38e0f5 e2612eb3
Loading
Loading
Loading
Loading
+75 −34
Original line number Diff line number Diff line
@@ -69,8 +69,12 @@ final class EphemeralResolverConnection implements DeathRecipient {
    /** 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;

@@ -137,23 +141,17 @@ final class EphemeralResolverConnection implements DeathRecipient {

    private IInstantAppResolver getRemoteInstanceLazy(String token)
            throws ConnectionException, TimeoutException, InterruptedException {
        synchronized (mLock) {
            if (mRemoteInstance != null) {
                return mRemoteInstance;
            }
        long binderToken = Binder.clearCallingIdentity();
        try {
                bindLocked(token);
            return bind(token);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
        }
            return mRemoteInstance;
        }
    }

    private void waitForBindLocked(String token) throws TimeoutException, InterruptedException {
        final long startMillis = SystemClock.uptimeMillis();
        while (mIsBinding) {
        while (mBindState != STATE_IDLE) {
            if (mRemoteInstance != null) {
                break;
            }
@@ -166,42 +164,83 @@ final class EphemeralResolverConnection implements DeathRecipient {
        }
    }

    private void bindLocked(String token)
    private IInstantAppResolver bind(String token)
            throws ConnectionException, TimeoutException, InterruptedException {
        if (DEBUG_EPHEMERAL && mIsBinding && mRemoteInstance == null) {
        boolean doUnbind = false;
        synchronized (mLock) {
            if (mRemoteInstance != null) {
                return mRemoteInstance;
            }

            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 (mRemoteInstance != null) {
            return;
        }
        mIsBinding = true;
            if (DEBUG_EPHEMERAL) {
                Slog.v(TAG, "[" + token + "] Binding to instant app resolver");
            }
        boolean wasBound = false;
        try {
            final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
            wasBound = mContext
                    .bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM);
            if (wasBound) {
                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;
            synchronized (mLock) {
                if (wasBound && instance == null) {
                    mBindState = STATE_PENDING;
                } else {
                    mBindState = STATE_IDLE;
                }
                mLock.notifyAll();
            }
        }
    }

    private void throwIfCalledOnMainThread() {
        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
@@ -255,7 +294,9 @@ final class EphemeralResolverConnection implements DeathRecipient {
            }
            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) {