Hide Notes Quick Affordance when Direct Boot user is locked

* Quick affordance should hide notes shortcut if direct boot user is locked;
* Once the user unlocks, notes is accessible from lock screen again;

For context:
* Quick affordance only evaluates the `lockScreenState` if the shortcut is in the lock screen. If a user changes the shortcut in Settings, the shortcut is not on the lock screen and `lockScreenState` does not matter.
* Notes is added as default shortcut for tablets but we keep it hidden. When a Stylus is used for the first time or the user set notes shortcut in Settings, `lockScreenState` will change to visible.
* A user can change the lock screen shortcut in Settings by: Settings -> Wallpaper & Style & Lock Screen Shortcuts.

Test: atest NoteTaskQuickAffordanceConfigTest

Fixes: b/266746835
Change-Id: I48dccd7784c1e00168afa723a32bcb96bf9dd13a
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 8aed995..2da5b76 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -18,6 +18,11 @@
 
 import android.content.Context
 import android.hardware.input.InputSettings
+import android.os.Build
+import android.os.UserManager
+import android.util.Log
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
@@ -39,6 +44,7 @@
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 class NoteTaskQuickAffordanceConfig
 @Inject
@@ -46,6 +52,8 @@
     context: Context,
     private val controller: NoteTaskController,
     private val stylusManager: StylusManager,
+    private val keyguardMonitor: KeyguardUpdateMonitor,
+    private val userManager: UserManager,
     private val lazyRepository: Lazy<KeyguardQuickAffordanceRepository>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
 ) : KeyguardQuickAffordanceConfig {
@@ -61,17 +69,27 @@
     // Due to a dependency cycle with KeyguardQuickAffordanceRepository, we need to lazily access
     // the repository when lockScreenState is accessed for the first time.
     override val lockScreenState by lazy {
-        val stylusEverUsedFlow = createStylusEverUsedFlow(context, stylusManager)
-        val configSelectedFlow = createConfigSelectedFlow(lazyRepository.get(), key)
-        combine(configSelectedFlow, stylusEverUsedFlow) { isSelected, isStylusEverUsed ->
-            if (isEnabled && (isSelected || isStylusEverUsed)) {
-                val contentDescription = ContentDescription.Resource(pickerNameResourceId)
-                val icon = Icon.Resource(pickerIconResourceId, contentDescription)
-                LockScreenState.Visible(icon)
-            } else {
-                LockScreenState.Hidden
+        val repository = lazyRepository.get()
+        val configSelectedFlow = repository.createConfigSelectedFlow(key)
+        val stylusEverUsedFlow = stylusManager.createStylusEverUsedFlow(context)
+        val userUnlockedFlow = userManager.createUserUnlockedFlow(keyguardMonitor)
+        combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow) {
+                isUserUnlocked,
+                isStylusEverUsed,
+                isConfigSelected ->
+                logDebug { "lockScreenState:isUserUnlocked=$isUserUnlocked" }
+                logDebug { "lockScreenState:isStylusEverUsed=$isStylusEverUsed" }
+                logDebug { "lockScreenState:isConfigSelected=$isConfigSelected" }
+
+                if (isEnabled && isUserUnlocked && (isConfigSelected || isStylusEverUsed)) {
+                    val contentDescription = ContentDescription.Resource(pickerNameResourceId)
+                    val icon = Icon.Resource(pickerIconResourceId, contentDescription)
+                    LockScreenState.Visible(icon)
+                } else {
+                    LockScreenState.Hidden
+                }
             }
-        }
+            .onEach { state -> logDebug { "lockScreenState=$state" } }
     }
 
     override suspend fun getPickerScreenState() =
@@ -82,27 +100,40 @@
         }
 
     override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
-        controller.showNoteTask(
-            entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE,
-        )
+        controller.showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
         return OnTriggeredResult.Handled
     }
 }
 
-private fun createStylusEverUsedFlow(context: Context, stylusManager: StylusManager) =
-    callbackFlow {
-        trySendBlocking(InputSettings.isStylusEverUsed(context))
-        val callback =
-            object : StylusManager.StylusCallback {
-                override fun onStylusFirstUsed() {
-                    trySendBlocking(InputSettings.isStylusEverUsed(context))
-                }
+private fun UserManager.createUserUnlockedFlow(monitor: KeyguardUpdateMonitor) = callbackFlow {
+    trySendBlocking(isUserUnlocked)
+    val callback =
+        object : KeyguardUpdateMonitorCallback() {
+            override fun onUserUnlocked() {
+                trySendBlocking(isUserUnlocked)
             }
-        stylusManager.registerCallback(callback)
-        awaitClose { stylusManager.unregisterCallback(callback) }
-    }
+        }
+    monitor.registerCallback(callback)
+    awaitClose { monitor.removeCallback(callback) }
+}
 
-private fun createConfigSelectedFlow(repository: KeyguardQuickAffordanceRepository, key: String) =
-    repository.selections.map { selected ->
+private fun StylusManager.createStylusEverUsedFlow(context: Context) = callbackFlow {
+    trySendBlocking(InputSettings.isStylusEverUsed(context))
+    val callback =
+        object : StylusManager.StylusCallback {
+            override fun onStylusFirstUsed() {
+                trySendBlocking(InputSettings.isStylusEverUsed(context))
+            }
+        }
+    registerCallback(callback)
+    awaitClose { unregisterCallback(callback) }
+}
+
+private fun KeyguardQuickAffordanceRepository.createConfigSelectedFlow(key: String) =
+    selections.map { selected ->
         selected.values.flatten().any { selectedConfig -> selectedConfig.key == key }
     }
+
+private inline fun Any.logDebug(message: () -> String) {
+    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName, message())
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index d44012f..42ef2b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -19,6 +19,7 @@
 package com.android.systemui.notetask.quickaffordance
 
 import android.hardware.input.InputSettings
+import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import com.android.dx.mockito.inline.extended.ExtendedMockito
@@ -33,6 +34,7 @@
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.android.systemui.stylus.StylusManager
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,6 +57,7 @@
     @Mock lateinit var controller: NoteTaskController
     @Mock lateinit var stylusManager: StylusManager
     @Mock lateinit var repository: KeyguardQuickAffordanceRepository
+    @Mock lateinit var userManager: UserManager
 
     private lateinit var mockitoSession: MockitoSession
 
@@ -66,12 +69,6 @@
                 .mockStatic(InputSettings::class.java)
                 .strictness(Strictness.LENIENT)
                 .startMocking()
-
-        whenever(InputSettings.isStylusEverUsed(mContext)).then { true }
-        whenever(repository.selections).then {
-            val map = mapOf("" to listOf(createUnderTest()))
-            MutableStateFlow(map)
-        }
     }
 
     @After
@@ -84,6 +81,8 @@
             context = context,
             controller = controller,
             stylusManager = stylusManager,
+            userManager = userManager,
+            keyguardMonitor = mock(),
             lazyRepository = { repository },
             isEnabled = isEnabled,
         )
@@ -98,47 +97,101 @@
                 )
         )
 
+    // region lockScreenState
     @Test
-    fun lockScreenState_stylusUsed_noCustomShortcutSelected_shouldEmitVisible() = runTest {
-        val underTest = createUnderTest()
+    fun lockScreenState_stylusUsed_userUnlocked_isSelected_shouldEmitVisible() = runTest {
+        TestConfig()
+            .setStylusEverUsed(true)
+            .setUserUnlocked(true)
+            .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
 
+        val underTest = createUnderTest()
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(createLockScreenStateVisible())
     }
 
     @Test
-    fun lockScreenState_noStylusEverUsed_noCustomShortcutSelected_shouldEmitVisible() = runTest {
-        whenever(InputSettings.isStylusEverUsed(mContext)).then { false }
-        val underTest = createUnderTest()
+    fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest {
+        TestConfig()
+            .setStylusEverUsed(false)
+            .setUserUnlocked(true)
+            .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
 
+        val underTest = createUnderTest()
+        val actual by collectLastValue(underTest.lockScreenState)
+
+        assertThat(actual).isEqualTo(LockScreenState.Hidden)
+    }
+
+    @Test
+    fun lockScreenState_stylusUsed_userLocked_isSelected_shouldEmitHidden() = runTest {
+        TestConfig()
+            .setStylusEverUsed(true)
+            .setUserUnlocked(false)
+            .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
+
+        val underTest = createUnderTest()
+        val actual by collectLastValue(underTest.lockScreenState)
+
+        assertThat(actual).isEqualTo(LockScreenState.Hidden)
+    }
+
+    @Test
+    fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitVisible() = runTest {
+        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections()
+
+        val underTest = createUnderTest()
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(createLockScreenStateVisible())
     }
 
     @Test
-    fun lockScreenState_stylusUsed_customShortcutSelected_shouldEmitVisible() = runTest {
-        whenever(repository.selections).then {
-            val map = mapOf<String, List<KeyguardQuickAffordanceConfig>>()
-            MutableStateFlow(map)
-        }
-        val underTest = createUnderTest()
+    fun lockScreenState_stylusUnused_userUnlocked_noSelected_shouldEmitHidden() = runTest {
+        TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections()
 
+        val underTest = createUnderTest()
+        val actual by collectLastValue(underTest.lockScreenState)
+
+        assertThat(actual).isEqualTo(LockScreenState.Hidden)
+    }
+
+    @Test
+    fun lockScreenState_stylusUsed_userLocked_noSelected_shouldEmitHidden() = runTest {
+        TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections()
+
+        val underTest = createUnderTest()
+        val actual by collectLastValue(underTest.lockScreenState)
+
+        assertThat(actual).isEqualTo(LockScreenState.Hidden)
+    }
+
+    @Test
+    fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitVisible() = runTest {
+        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(mock())
+
+        val underTest = createUnderTest()
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(createLockScreenStateVisible())
     }
 
     @Test
-    fun lockScreenState_noIsStylusEverUsed_noCustomShortcutSelected_shouldEmitHidden() = runTest {
-        whenever(InputSettings.isStylusEverUsed(mContext)).then { false }
-        whenever(repository.selections).then {
-            val map = mapOf<String, List<KeyguardQuickAffordanceConfig>>()
-            MutableStateFlow(map)
-        }
-        val underTest = createUnderTest()
+    fun lockScreenState_stylusUnused_userUnlocked_customSelections_shouldEmitHidden() = runTest {
+        TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(mock())
 
+        val underTest = createUnderTest()
+        val actual by collectLastValue(underTest.lockScreenState)
+
+        assertThat(actual).isEqualTo(LockScreenState.Hidden)
+    }
+
+    @Test
+    fun lockScreenState_stylusUsed_userLocked_customSelections_shouldEmitHidden() = runTest {
+        TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(mock())
+
+        val underTest = createUnderTest()
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(LockScreenState.Hidden)
@@ -146,12 +199,14 @@
 
     @Test
     fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest {
-        val underTest = createUnderTest(isEnabled = false)
+        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections()
 
+        val underTest = createUnderTest(isEnabled = false)
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(LockScreenState.Hidden)
     }
+    // endregion
 
     @Test
     fun onTriggered_shouldLaunchNoteTask() {
@@ -161,4 +216,22 @@
 
         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
     }
+
+    private inner class TestConfig {
+
+        fun setStylusEverUsed(value: Boolean) = also {
+            whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(value)
+        }
+
+        fun setUserUnlocked(value: Boolean) = also {
+            whenever(userManager.isUserUnlocked).thenReturn(value)
+        }
+
+        fun setConfigSelections(vararg values: KeyguardQuickAffordanceConfig) = also {
+            val slotKey = "bottom-right"
+            val configSnapshots = values.toList()
+            val map = mapOf(slotKey to configSnapshots)
+            whenever(repository.selections).thenReturn(MutableStateFlow(map))
+        }
+    }
 }