diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt new file mode 100644 index 0000000000..7b49583b86 --- /dev/null +++ b/src/com/android/launcher3/util/LockedUserState.kt @@ -0,0 +1,57 @@ +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) + } +} diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt new file mode 100644 index 0000000000..84156e7101 --- /dev/null +++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt @@ -0,0 +1,88 @@ +/* + * 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() + } +}