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

Commit 67104ffc authored by Yi Jiang's avatar Yi Jiang Committed by Android (Google) Code Review
Browse files

Merge "Removes UserState from AttentionManagerService."

parents 28494ad4 c0442323
Loading
Loading
Loading
Loading
+133 −219
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNO
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
import android.content.BroadcastReceiver;
@@ -55,7 +54,6 @@ import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -101,17 +99,26 @@ public class AttentionManagerService extends SystemService {
    @VisibleForTesting
    protected static final int ATTENTION_CACHE_BUFFER_SIZE = 5;

    private final AttentionServiceConnection mConnection = new AttentionServiceConnection();
    private static String sTestAttentionServicePackage;
    private final Context mContext;
    private final PowerManager mPowerManager;
    private final Object mLock;
    @GuardedBy("mLock")
    private final SparseArray<UserState> mUserStates = new SparseArray<>();
    private IAttentionService mService;
    @GuardedBy("mLock")
    private AttentionCheckCacheBuffer mAttentionCheckCacheBuffer;
    @GuardedBy("mLock")
    private boolean mBinding;
    private AttentionHandler mAttentionHandler;

    @VisibleForTesting
    ComponentName mComponentName;

    @VisibleForTesting
    @GuardedBy("mLock")
    AttentionCheck mCurrentAttentionCheck;

    public AttentionManagerService(Context context) {
        this(context, (PowerManager) context.getSystemService(Context.POWER_SERVICE),
                new Object(), null);
@@ -142,11 +149,6 @@ public class AttentionManagerService extends SystemService {
        publishLocalService(AttentionManagerInternal.class, new LocalService());
    }

    @Override
    public void onSwitchUser(int userId) {
        cancelAndUnbindLocked(peekUserStateLocked(userId));
    }

    /** Returns {@code true} if attention service is configured on this device. */
    public static boolean isServiceConfigured(Context context) {
        return !TextUtils.isEmpty(getServiceConfigPackage(context));
@@ -219,35 +221,33 @@ public class AttentionManagerService extends SystemService {
            // schedule shutting down the connection if no one resets this timer
            freeIfInactiveLocked();

            final UserState userState = getOrCreateCurrentUserStateLocked();
            // lazily start the service, which should be very lightweight to start
            userState.bindLocked();
            bindLocked();

            // throttle frequent requests
            final AttentionCheckCache cache = userState.mAttentionCheckCacheBuffer == null ? null
                    : userState.mAttentionCheckCacheBuffer.getLast();
            final AttentionCheckCache cache = mAttentionCheckCacheBuffer == null ? null
                    : mAttentionCheckCacheBuffer.getLast();
            if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
                callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
                return true;
            }

            // prevent spamming with multiple requests, only one at a time is allowed
            if (userState.mCurrentAttentionCheck != null) {
                if (!userState.mCurrentAttentionCheck.mIsDispatched
                        || !userState.mCurrentAttentionCheck.mIsFulfilled) {
            if (mCurrentAttentionCheck != null) {
                if (!mCurrentAttentionCheck.mIsDispatched
                        || !mCurrentAttentionCheck.mIsFulfilled) {
                    return false;
                }
            }

            userState.mCurrentAttentionCheck = new AttentionCheck(callbackInternal, userState);
            mCurrentAttentionCheck = new AttentionCheck(callbackInternal, this);

            if (userState.mService != null) {
            if (mService != null) {
                try {
                    // schedule request cancellation if not returned by that point yet
                    cancelAfterTimeoutLocked(timeout);
                    userState.mService.checkAttention(
                            userState.mCurrentAttentionCheck.mIAttentionCallback);
                    userState.mCurrentAttentionCheck.mIsDispatched = true;
                    mService.checkAttention(mCurrentAttentionCheck.mIAttentionCallback);
                    mCurrentAttentionCheck.mIsDispatched = true;
                } catch (RemoteException e) {
                    Slog.e(LOG_TAG, "Cannot call into the AttentionService");
                    return false;
@@ -261,15 +261,11 @@ public class AttentionManagerService extends SystemService {
    @VisibleForTesting
    void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) {
        synchronized (mLock) {
            final UserState userState = peekCurrentUserStateLocked();
            if (userState == null) {
                return;
            }
            if (!userState.mCurrentAttentionCheck.mCallbackInternal.equals(callbackInternal)) {
            if (!mCurrentAttentionCheck.mCallbackInternal.equals(callbackInternal)) {
                Slog.w(LOG_TAG, "Cannot cancel a non-current request");
                return;
            }
            cancel(userState);
            cancel();
        }
    }

@@ -290,39 +286,6 @@ public class AttentionManagerService extends SystemService {
                timeout);
    }


    @GuardedBy("mLock")
    @VisibleForTesting
    protected UserState getOrCreateCurrentUserStateLocked() {
        // Doesn't need to cache the states of different users.
        return getOrCreateUserStateLocked(0);
    }

    @GuardedBy("mLock")
    @VisibleForTesting
    protected UserState getOrCreateUserStateLocked(int userId) {
        UserState result = mUserStates.get(userId);
        if (result == null) {
            result = new UserState(userId, mContext, mLock, mAttentionHandler, mComponentName);
            mUserStates.put(userId, result);
        }
        return result;
    }

    @GuardedBy("mLock")
    @Nullable
    @VisibleForTesting
    protected UserState peekCurrentUserStateLocked() {
        // Doesn't need to cache the states of different users.
        return peekUserStateLocked(0);
    }

    @GuardedBy("mLock")
    @Nullable
    private UserState peekUserStateLocked(int userId) {
        return mUserStates.get(userId);
    }

    private static String getServiceConfigPackage(Context context) {
        return context.getPackageManager().getAttentionServicePackageName();
    }
@@ -380,21 +343,14 @@ public class AttentionManagerService extends SystemService {
            ipw.println("Class=" + mComponentName.getClassName());
            ipw.decreaseIndent();
        }

        ipw.println("binding=" + mBinding);
        ipw.println("current attention check:");
        synchronized (mLock) {
            int size = mUserStates.size();
            ipw.print("Number user states: ");
            ipw.println(size);
            if (size > 0) {
                ipw.increaseIndent();
                for (int i = 0; i < size; i++) {
                    UserState userState = mUserStates.valueAt(i);
                    ipw.print(i);
                    ipw.print(":");
                    userState.dump(ipw);
                    ipw.println();
            if (mCurrentAttentionCheck != null) {
                mCurrentAttentionCheck.dump(ipw);
            }
                ipw.decreaseIndent();
            if (mAttentionCheckCacheBuffer != null) {
                mAttentionCheckCacheBuffer.dump(ipw);
            }
        }
    }
@@ -447,6 +403,20 @@ public class AttentionManagerService extends SystemService {
            return offset >= mSize ? null
                    : mQueue[(mStartIndex + offset) % ATTENTION_CACHE_BUFFER_SIZE];
        }

        private void dump(IndentingPrintWriter ipw) {
            ipw.println("attention check cache:");
            AttentionCheckCache cache;
            for (int i = 0; i < mSize; i++) {
                cache = get(i);
                if (cache != null) {
                    ipw.increaseIndent();
                    ipw.println("timestamp=" + cache.mTimestamp);
                    ipw.println("result=" + cache.mResult);
                    ipw.decreaseIndent();
                }
            }
        }
    }

    @VisibleForTesting
@@ -471,7 +441,8 @@ public class AttentionManagerService extends SystemService {
        private boolean mIsDispatched;
        private boolean mIsFulfilled;

        AttentionCheck(AttentionCallbackInternal callbackInternal, UserState userState) {
        AttentionCheck(AttentionCallbackInternal callbackInternal,
                AttentionManagerService service) {
            mCallbackInternal = callbackInternal;
            mIAttentionCallback = new IAttentionCallback.Stub() {
                @Override
@@ -482,7 +453,7 @@ public class AttentionManagerService extends SystemService {
                    mIsFulfilled = true;
                    callbackInternal.onSuccess(result, timestamp);
                    logStats(result);
                    userState.appendResultToAttentionCacheBuffer(
                    service.appendResultToAttentionCacheBuffer(
                            new AttentionCheckCache(SystemClock.uptimeMillis(), result,
                                    timestamp));
                }
@@ -509,96 +480,12 @@ public class AttentionManagerService extends SystemService {
            mIsFulfilled = true;
            mCallbackInternal.onFailure(ATTENTION_FAILURE_CANCELLED);
        }
    }

    @VisibleForTesting
    protected static class UserState {
        private final ComponentName mComponentName;
        private final AttentionServiceConnection mConnection = new AttentionServiceConnection();

        @GuardedBy("mLock")
        IAttentionService mService;
        @GuardedBy("mLock")
        AttentionCheck mCurrentAttentionCheck;
        @GuardedBy("mLock")
        AttentionCheckCacheBuffer mAttentionCheckCacheBuffer;
        @GuardedBy("mLock")
        private boolean mBinding;

        @UserIdInt
        private final int mUserId;
        private final Context mContext;
        private final Object mLock;
        private final Handler mAttentionHandler;

        UserState(int userId, Context context, Object lock, Handler handler,
                ComponentName componentName) {
            mUserId = userId;
            mContext = Objects.requireNonNull(context);
            mLock = Objects.requireNonNull(lock);
            mComponentName = Objects.requireNonNull(componentName);
            mAttentionHandler = handler;
        }

        @GuardedBy("mLock")
        private void handlePendingCallbackLocked() {
            if (!mCurrentAttentionCheck.mIsDispatched) {
                if (mService != null) {
                    try {
                        mService.checkAttention(mCurrentAttentionCheck.mIAttentionCallback);
                        mCurrentAttentionCheck.mIsDispatched = true;
                    } catch (RemoteException e) {
                        Slog.e(LOG_TAG, "Cannot call into the AttentionService");
                    }
                } else {
                    mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN);
                }
            }
        }

        /** Binds to the system's AttentionService which provides an actual implementation. */
        @GuardedBy("mLock")
        private void bindLocked() {
            // No need to bind if service is binding or has already been bound.
            if (mBinding || mService != null) {
                return;
            }

            mBinding = true;
            // mContext.bindServiceAsUser() calls into ActivityManagerService which it may already
            // hold the lock and had called into PowerManagerService, which holds a lock.
            // That would create a deadlock. To solve that, putting it on a handler.
            mAttentionHandler.post(() -> {
                final Intent serviceIntent = new Intent(
                        AttentionService.SERVICE_INTERFACE).setComponent(
                        mComponentName);
                // Note: no reason to clear the calling identity, we won't have one in a handler.
                mContext.bindServiceAsUser(serviceIntent, mConnection,
                        Context.BIND_AUTO_CREATE, UserHandle.CURRENT);

            });
        }

        private void dump(IndentingPrintWriter pw) {
            pw.println("userId=" + mUserId);
            synchronized (mLock) {
                pw.println("binding=" + mBinding);
                pw.println("current attention check:");
                if (mCurrentAttentionCheck != null) {
                    pw.increaseIndent();
                    pw.println("is dispatched=" + mCurrentAttentionCheck.mIsDispatched);
                    pw.println("is fulfilled:=" + mCurrentAttentionCheck.mIsFulfilled);
                    pw.decreaseIndent();
                }
                if (mAttentionCheckCacheBuffer != null) {
                    pw.println("attention check cache:");
                    for (int i = 0; i < mAttentionCheckCacheBuffer.mSize; i++) {
                        pw.increaseIndent();
                        pw.println("timestamp=" + mAttentionCheckCacheBuffer.get(i).mTimestamp);
                        pw.println("result=" + mAttentionCheckCacheBuffer.get(i).mResult);
                        pw.decreaseIndent();
                    }
                }
        void dump(IndentingPrintWriter ipw) {
            ipw.increaseIndent();
            ipw.println("is dispatched=" + mIsDispatched);
            ipw.println("is fulfilled:=" + mIsFulfilled);
            ipw.decreaseIndent();
        }
    }

@@ -645,6 +532,21 @@ public class AttentionManagerService extends SystemService {
            }
        }
    }

    @GuardedBy("mLock")
    private void handlePendingCallbackLocked() {
        if (mCurrentAttentionCheck != null && !mCurrentAttentionCheck.mIsDispatched) {
            if (mService != null) {
                try {
                    mService.checkAttention(mCurrentAttentionCheck.mIAttentionCallback);
                    mCurrentAttentionCheck.mIsDispatched = true;
                } catch (RemoteException e) {
                    Slog.e(LOG_TAG, "Cannot call into the AttentionService");
                }
            } else {
                mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN);
            }
        }
    }

    @VisibleForTesting
@@ -661,16 +563,14 @@ public class AttentionManagerService extends SystemService {
            switch (msg.what) {
                // Do not occupy resources when not in use - unbind proactively.
                case CHECK_CONNECTION_EXPIRATION: {
                    for (int i = 0; i < mUserStates.size(); i++) {
                        cancelAndUnbindLocked(mUserStates.valueAt(i));
                    }
                    cancelAndUnbindLocked();
                }
                break;

                // Callee is no longer interested in the attention check result - cancel.
                case ATTENTION_CHECK_TIMEOUT: {
                    synchronized (mLock) {
                        cancel(peekCurrentUserStateLocked());
                        cancel();
                    }
                }
                break;
@@ -682,54 +582,69 @@ public class AttentionManagerService extends SystemService {
    }

    @VisibleForTesting
    void cancel(UserState userState) {
        if (userState == null || userState.mCurrentAttentionCheck == null) {
            return;
        }

        if (userState.mCurrentAttentionCheck.mIsFulfilled) {
    @GuardedBy("mLock")
    void cancel() {
        if (mCurrentAttentionCheck.mIsFulfilled) {
            if (DEBUG) {
                Slog.d(LOG_TAG, "Trying to cancel the check that has been already fulfilled.");
            }
            return;
        }

        if (userState.mService == null) {
            userState.mCurrentAttentionCheck.cancelInternal();
        if (mService == null) {
            mCurrentAttentionCheck.cancelInternal();
            return;
        }

        try {
            userState.mService.cancelAttentionCheck(
                    userState.mCurrentAttentionCheck.mIAttentionCallback);
            mService.cancelAttentionCheck(mCurrentAttentionCheck.mIAttentionCallback);
        } catch (RemoteException e) {
            Slog.e(LOG_TAG, "Unable to cancel attention check");
            userState.mCurrentAttentionCheck.cancelInternal();
            mCurrentAttentionCheck.cancelInternal();
        }
    }

    @GuardedBy("mLock")
    private void cancelAndUnbindLocked(UserState userState) {
    private void cancelAndUnbindLocked() {
        synchronized (mLock) {
            if (userState == null) {
            if (mCurrentAttentionCheck == null) {
                return;
            }

            cancel(userState);

            if (userState.mService == null) {
            cancel();
            if (mService == null) {
                return;
            }

            mAttentionHandler.post(() -> mContext.unbindService(userState.mConnection));
            mAttentionHandler.post(() -> mContext.unbindService(mConnection));
            // Note: this will set mBinding to false even though it could still be trying to bind
            // (i.e. the runnable was posted in bindLocked but then cancelAndUnbindLocked was
            // called before it's run yet). This is a safe state at the moment,
            // since it will eventually, but feels like a source for confusion down the road and
            // may cause some expensive and unnecessary work to be done.
            userState.mConnection.cleanupService();
            mUserStates.remove(userState.mUserId);
            mConnection.cleanupService();
        }
    }

    /** Binds to the system's AttentionService which provides an actual implementation. */
    @GuardedBy("mLock")
    private void bindLocked() {
        // No need to bind if service is binding or has already been bound.
        if (mBinding || mService != null) {
            return;
        }

        mBinding = true;
        // mContext.bindServiceAsUser() calls into ActivityManagerService which it may already
        // hold the lock and had called into PowerManagerService, which holds a lock.
        // That would create a deadlock. To solve that, putting it on a handler.
        mAttentionHandler.post(() -> {
            final Intent serviceIntent = new Intent(
                    AttentionService.SERVICE_INTERFACE).setComponent(
                    mComponentName);
            // Note: no reason to clear the calling identity, we won't have one in a handler.
            mContext.bindServiceAsUser(serviceIntent, mConnection,
                    Context.BIND_AUTO_CREATE, UserHandle.CURRENT);

        });
    }

    /**
@@ -740,7 +655,7 @@ public class AttentionManagerService extends SystemService {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                cancelAndUnbindLocked(peekCurrentUserStateLocked());
                cancelAndUnbindLocked();
            }
        }
    }
@@ -853,7 +768,6 @@ public class AttentionManagerService extends SystemService {

        private void resetStates() {
            mComponentName = resolveAttentionService(mContext);
            mUserStates.clear();
        }

        @Override
+7 −33
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
@@ -50,7 +49,6 @@ import com.android.server.attention.AttentionManagerService.AttentionCheck;
import com.android.server.attention.AttentionManagerService.AttentionCheckCache;
import com.android.server.attention.AttentionManagerService.AttentionCheckCacheBuffer;
import com.android.server.attention.AttentionManagerService.AttentionHandler;
import com.android.server.attention.AttentionManagerService.UserState;

import org.junit.Before;
import org.junit.Test;
@@ -64,15 +62,12 @@ import org.mockito.MockitoAnnotations;
@SmallTest
public class AttentionManagerServiceTest {
    private AttentionManagerService mSpyAttentionManager;
    private UserState mSpyUserState;
    private final int mTimeout = 1000;
    @Mock
    private AttentionCallbackInternal mMockAttentionCallbackInternal;
    @Mock
    private AttentionHandler mMockHandler;
    @Mock
    private UserState mMockUserState;
    @Mock
    private IPowerManager mMockIPowerManager;
    @Mock
    private IThermalService mMockIThermalService;
@@ -91,44 +86,31 @@ public class AttentionManagerServiceTest {

        Object mLock = new Object();
        // setup a spy on attention manager
        AttentionManagerService mAttentionManager = new AttentionManagerService(
        AttentionManagerService attentionManager = new AttentionManagerService(
                mContext,
                mPowerManager,
                mLock,
                mMockHandler);
        mSpyAttentionManager = Mockito.spy(mAttentionManager);
        mSpyAttentionManager = Mockito.spy(attentionManager);
        // setup a spy on user state
        ComponentName componentName = new ComponentName("a", "b");
        mSpyAttentionManager.mComponentName = componentName;
        UserState mUserState = new UserState(0,
                mContext,
                mLock,
                mMockHandler,
                componentName);
        mUserState.mService = new MockIAttentionService();
        mSpyUserState = spy(mUserState);
    }

    @Test
    public void testCancelAttentionCheck_noCrashWhenNoUserStateLocked() {
        mSpyAttentionManager.cancelAttentionCheck(null);
        AttentionCheck attentionCheck = new AttentionCheck(mMockAttentionCallbackInternal,
                mSpyAttentionManager);
        mSpyAttentionManager.mCurrentAttentionCheck = attentionCheck;
    }

    @Test
    public void testCancelAttentionCheck_noCrashWhenCallbackMismatched() {
        mSpyUserState.mCurrentAttentionCheck =
                new AttentionCheck(mMockAttentionCallbackInternal, mMockUserState);
        doReturn(mSpyUserState).when(mSpyAttentionManager).peekCurrentUserStateLocked();
        assertThat(mMockAttentionCallbackInternal).isNotNull();
        mSpyAttentionManager.cancelAttentionCheck(null);
    }

    @Test
    public void testCancelAttentionCheck_cancelCallbackWhenMatched() {
        mSpyUserState.mCurrentAttentionCheck =
                new AttentionCheck(mMockAttentionCallbackInternal, mMockUserState);
        doReturn(mSpyUserState).when(mSpyAttentionManager).peekCurrentUserStateLocked();
        mSpyAttentionManager.cancelAttentionCheck(mMockAttentionCallbackInternal);
        verify(mSpyAttentionManager).cancel(any());
        verify(mSpyAttentionManager).cancel();
    }

    @Test
@@ -142,7 +124,6 @@ public class AttentionManagerServiceTest {
    public void testCheckAttention_callOnSuccess() throws RemoteException {
        doReturn(true).when(mSpyAttentionManager).isServiceEnabled();
        doReturn(true).when(mMockIPowerManager).isInteractive();
        doReturn(mSpyUserState).when(mSpyAttentionManager).getOrCreateCurrentUserStateLocked();
        doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();

        AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class);
@@ -150,13 +131,6 @@ public class AttentionManagerServiceTest {
        verify(callback).onSuccess(anyInt(), anyLong());
    }

    @Test
    public void testOnSwitchUser_noCrashCurrentServiceIsNull() {
        final int userId = 10;
        mSpyAttentionManager.getOrCreateUserStateLocked(userId);
        mSpyAttentionManager.onSwitchUser(userId);
    }

    @Test
    public void testAttentionCheckCacheBuffer_getLast_returnTheLastElement() {
        AttentionCheckCacheBuffer buffer = new AttentionCheckCacheBuffer();