Loading quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +2 −49 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading Loading @@ -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 */ Loading @@ -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 */ Loading Loading @@ -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()); Loading quickstep/src/com/android/quickstep/TouchInteractionService.java +14 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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()); } Loading @@ -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(); } Loading Loading @@ -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; } Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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 = Loading Loading @@ -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); Loading src/com/android/launcher3/util/LockedUserState.kt 0 → 100644 +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) } } tests/src/com/android/launcher3/util/LockedUserStateTest.kt 0 → 100644 +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() } } Loading
quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +2 −49 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading Loading @@ -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 */ Loading @@ -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 */ Loading Loading @@ -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()); Loading
quickstep/src/com/android/quickstep/TouchInteractionService.java +14 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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()); } Loading @@ -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(); } Loading Loading @@ -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; } Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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 = Loading Loading @@ -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); Loading
src/com/android/launcher3/util/LockedUserState.kt 0 → 100644 +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) } }
tests/src/com/android/launcher3/util/LockedUserStateTest.kt 0 → 100644 +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() } }