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))
+ }
+ }
}