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

Commit 2a58ddb8 authored by Stefan Andonian's avatar Stefan Andonian
Browse files

Refactor the OnUserUnlock code out of RecentsAnimationDeviceState and

into its own class.

Bug: 251502424
Test: Compilation threw no errors and user unlock behavior worked
correctly.

Change-Id: Ifa42dc32f90dfa4fda8df0e52811ddfe20cc5a9b
parent 83a8dad3
Loading
Loading
Loading
Loading
+2 −49
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.quickstep;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.view.Display.DEFAULT_DISPLAY;

import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
@@ -50,10 +49,8 @@ import android.content.Context;
import android.graphics.Region;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
import android.view.MotionEvent;

@@ -63,9 +60,9 @@ import androidx.annotation.NonNull;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.NavBarPosition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -109,15 +106,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
    private final boolean mIsOneHandedModeSupported;
    private boolean mPipIsActive;

    private boolean mIsUserUnlocked;
    private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
    private final SimpleBroadcastReceiver mUserUnlockedReceiver = new SimpleBroadcastReceiver(i -> {
        if (ACTION_USER_UNLOCKED.equals(i.getAction())) {
            mIsUserUnlocked = true;
            notifyUserUnlocked();
        }
    });

    private int mGestureBlockingTaskId = -1;
    private @NonNull Region mExclusionRegion = new Region();
    private SystemGestureExclusionListenerCompat mExclusionListener;
@@ -143,14 +131,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
            runOnDestroy(mRotationTouchHelper::destroy);
        }

        // Register for user unlocked if necessary
        mIsUserUnlocked = context.getSystemService(UserManager.class)
                .isUserUnlocked(Process.myUserHandle());
        if (!mIsUserUnlocked) {
            mUserUnlockedReceiver.register(mContext, ACTION_USER_UNLOCKED);
        }
        runOnDestroy(() -> mUserUnlockedReceiver.unregisterReceiverSafely(mContext));

        // Register for exclusion updates
        mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
            @Override
@@ -309,25 +289,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
        return mDisplayId;
    }

    /**
     * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
     * will be called back immediately.
     */
    public void runOnUserUnlocked(Runnable action) {
        if (mIsUserUnlocked) {
            action.run();
        } else {
            mUserUnlockedActions.add(action);
        }
    }

    /**
     * @return whether the user is unlocked.
     */
    public boolean isUserUnlocked() {
        return mIsUserUnlocked;
    }

    /**
     * @return whether the user has completed setup wizard
     */
@@ -335,14 +296,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
        return mIsUserSetupComplete;
    }

    private void notifyUserUnlocked() {
        for (Runnable action : mUserUnlockedActions) {
            action.run();
        }
        mUserUnlockedActions.clear();
        mUserUnlockedReceiver.unregisterReceiverSafely(mContext);
    }

    /**
     * Sets the task id where gestures should be blocked
     */
@@ -585,7 +538,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
        pw.println("  assistantAvailable=" + mAssistantAvailable);
        pw.println("  assistantDisabled="
                + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
        pw.println("  isUserUnlocked=" + mIsUserUnlocked);
        pw.println("  isUserUnlocked=" + LockedUserState.get(mContext).isUserUnlocked());
        pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
        pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
        pw.println("  deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
+14 −12
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import com.android.launcher3.tracing.LauncherTraceProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
@@ -406,8 +407,8 @@ public class TouchInteractionService extends Service
        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();

        // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
        mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
        mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
        LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
        LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
        mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);

        ProtoTracer.INSTANCE.get(this).add(this);
@@ -477,7 +478,7 @@ public class TouchInteractionService extends Service
    }

    private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
        if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
        if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
            // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
            // mode doesn't have gestures
            return;
@@ -520,7 +521,7 @@ public class TouchInteractionService extends Service

    @UiThread
    private void onSystemUiFlagsChanged(int lastSysUIFlags) {
        if (mDeviceState.isUserUnlocked()) {
        if (LockedUserState.get(this).isUserUnlocked()) {
            int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
            SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
            mOverviewComponentObserver.onSystemUiStateChanged();
@@ -565,7 +566,7 @@ public class TouchInteractionService extends Service

    @UiThread
    private void onAssistantVisibilityChanged() {
        if (mDeviceState.isUserUnlocked()) {
        if (LockedUserState.get(this).isUserUnlocked()) {
            mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
                    mDeviceState.getAssistantVisibility());
        }
@@ -575,7 +576,7 @@ public class TouchInteractionService extends Service
    public void onDestroy() {
        Log.d(TAG, "Touch service destroyed: user=" + getUserId());
        sIsInitialized = false;
        if (mDeviceState.isUserUnlocked()) {
        if (LockedUserState.get(this).isUserUnlocked()) {
            mInputConsumer.unregisterInputConsumer();
            mOverviewComponentObserver.onDestroy();
        }
@@ -609,7 +610,7 @@ public class TouchInteractionService extends Service
        TestLogging.recordMotionEvent(
                TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);

        if (!mDeviceState.isUserUnlocked()) {
        if (!LockedUserState.get(this).isUserUnlocked()) {
            return;
        }

@@ -631,7 +632,8 @@ public class TouchInteractionService extends Service
                mGestureState = newGestureState;
                mConsumer = newConsumer(prevGestureState, mGestureState, event);
                mUncheckedConsumer = mConsumer;
            } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
            } else if (LockedUserState.get(this).isUserUnlocked()
                    && mDeviceState.isFullyGesturalNavMode()
                    && mDeviceState.canTriggerAssistantAction(event)) {
                mGestureState = createGestureState(mGestureState);
                // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
@@ -751,7 +753,7 @@ public class TouchInteractionService extends Service

        boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();

        if (!mDeviceState.isUserUnlocked()) {
        if (!LockedUserState.get(this).isUserUnlocked()) {
            CompoundString reasonString = newCompoundString("device locked");
            InputConsumer consumer;
            if (canStartSystemGesture) {
@@ -1098,7 +1100,7 @@ public class TouchInteractionService extends Service
    }

    private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
        if (!mDeviceState.isUserUnlocked()) {
        if (!LockedUserState.get(this).isUserUnlocked()) {
            return;
        }

@@ -1130,7 +1132,7 @@ public class TouchInteractionService extends Service

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        if (!mDeviceState.isUserUnlocked()) {
        if (!LockedUserState.get(this).isUserUnlocked()) {
            return;
        }
        final BaseActivityInterface activityInterface =
@@ -1171,7 +1173,7 @@ public class TouchInteractionService extends Service
        } else {
            // Dump everything
            FeatureFlags.dump(pw);
            if (mDeviceState.isUserUnlocked()) {
            if (LockedUserState.get(this).isUserUnlocked()) {
                PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
            }
            mDeviceState.dump(pw);
+57 −0
Original line number Diff line number Diff line
package com.android.launcher3.util

import android.content.Context
import android.content.Intent
import android.os.Process
import android.os.UserManager
import androidx.annotation.VisibleForTesting

class LockedUserState(private val mContext: Context) : SafeCloseable {
    var isUserUnlocked: Boolean
        private set
    private val mUserUnlockedActions: RunnableList = RunnableList()

    @VisibleForTesting
    val mUserUnlockedReceiver = SimpleBroadcastReceiver {
        if (Intent.ACTION_USER_UNLOCKED == it.action) {
            isUserUnlocked = true
            notifyUserUnlocked()
        }
    }

    init {
        isUserUnlocked =
            mContext
                .getSystemService(UserManager::class.java)!!
                .isUserUnlocked(Process.myUserHandle())
        if (isUserUnlocked) {
            notifyUserUnlocked()
        } else {
            mUserUnlockedReceiver.register(mContext, Intent.ACTION_USER_UNLOCKED)
        }
    }

    private fun notifyUserUnlocked() {
        mUserUnlockedActions.executeAllAndDestroy()
        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
    }

    /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
    override fun close() {
        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
    }

    /**
     * Adds a `Runnable` to be executed when a user is unlocked. If the user is already unlocked,
     * this runnable will run immediately because RunnableList will already have been destroyed.
     */
    fun runOnUserUnlocked(action: Runnable) {
        mUserUnlockedActions.add(action)
    }

    companion object {
        @VisibleForTesting val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }

        @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
    }
}
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.util

import android.content.Context
import android.content.Intent
import android.os.Process
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations

/** Unit tests for {@link LockedUserUtil} */
@SmallTest
@RunWith(AndroidJUnit4::class)
class LockedUserStateTest {

    @Mock lateinit var userManager: UserManager
    @Mock lateinit var context: Context

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
    }

    @Test
    fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
        val action: Runnable = mock()

        LockedUserState.get(context).runOnUserUnlocked(action)
        verify(action).run()
    }

    @Test
    fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
        val action: Runnable = mock()

        LockedUserState.get(context).runOnUserUnlocked(action)
        verifyZeroInteractions(action)

        LockedUserState.get(context)
            .mUserUnlockedReceiver
            .onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))

        verify(action).run()
    }

    @Test
    fun isUserUnlocked_returns_true_when_user_is_unlocked() {
        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
        assertThat(LockedUserState.get(context).isUserUnlocked).isTrue()
    }

    @Test
    fun isUserUnlocked_returns_false_when_user_is_locked() {
        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
        assertThat(LockedUserState.get(context).isUserUnlocked).isFalse()
    }
}