Only show the DND icon in Weather Clock if the DND mode is active
For other modes, show nothing at all (because it's not clear we're allowed to show 3P-provided icons).
Fixes: 369154614
Test: atest ClockEventControllerTest + manual
Flag: android.app.modes_ui
Change-Id: I878bc6fc9186662a0290b6416a9b1e326b0b912f
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index c686708..43d7946 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -74,6 +74,10 @@
mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
}
+ fun replaceMode(modeId: String, mode: ZenMode) {
+ mutableModesFlow.value = (mutableModesFlow.value.filter { it.id != modeId }) + mode
+ }
+
fun getMode(id: String): ZenMode? {
return mutableModesFlow.value.find { it.id == id }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index c1eae2e..e5cf7cf 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,12 +15,15 @@
*/
package com.android.keyguard
+import android.app.NotificationManager.zenModeFromInterruptionFilter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
import android.os.Trace
+import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+import android.provider.Settings.Global.ZEN_MODE_OFF
import android.text.format.DateFormat
import android.util.Log
import android.util.TypedValue
@@ -49,6 +52,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.core.Logger
+import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
@@ -63,6 +67,7 @@
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.concurrency.DelayableExecutor
import java.util.Locale
import java.util.TimeZone
@@ -97,12 +102,13 @@
private val clockBuffers: ClockMessageBuffers,
private val featureFlags: FeatureFlagsClassic,
private val zenModeController: ZenModeController,
+ private val zenModeInteractor: ZenModeInteractor,
) {
var loggers =
listOf(
clockBuffers.infraMessageBuffer,
clockBuffers.smallClockMessageBuffer,
- clockBuffers.largeClockMessageBuffer
+ clockBuffers.largeClockMessageBuffer,
)
.map { Logger(it, TAG) }
@@ -146,7 +152,7 @@
bgExecutor,
regionSamplingEnabled,
isLockscreen = true,
- ::updateColors
+ ::updateColors,
)
.apply { startRegionSampler() }
@@ -157,7 +163,7 @@
bgExecutor,
regionSamplingEnabled,
isLockscreen = true,
- ::updateColors
+ ::updateColors,
)
.apply { startRegionSampler() }
@@ -271,7 +277,7 @@
bgExecutor: Executor?,
regionSamplingEnabled: Boolean,
isLockscreen: Boolean,
- updateColors: () -> Unit
+ updateColors: () -> Unit,
): RegionSampler {
return RegionSampler(
sampledView,
@@ -384,24 +390,30 @@
}
}
+ @VisibleForTesting
+ internal fun listenForDnd(scope: CoroutineScope): Job {
+ ModesUi.assertInNewMode()
+ return scope.launch {
+ zenModeInteractor.dndMode.collect {
+ val zenMode =
+ if (it != null && it.isActive)
+ zenModeFromInterruptionFilter(
+ it.interruptionFilter,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ )
+ else ZEN_MODE_OFF
+
+ handleZenMode(zenMode)
+ }
+ }
+ }
+
private val zenModeCallback =
object : ZenModeController.Callback {
override fun onZenChanged(zen: Int) {
- var mode = ZenMode.fromInt(zen)
- if (mode == null) {
- Log.e(TAG, "Failed to get zen mode from int: $zen")
- return
+ if (!ModesUi.isEnabled) {
+ handleZenMode(zen)
}
-
- zenData =
- ZenData(
- mode,
- if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name
- else SysuiR.string::dnd_is_on.name
- )
- .also { data ->
- mainExecutor.execute { clock?.run { events.onZenDataChanged(data) } }
- }
}
override fun onNextAlarmChanged() {
@@ -409,7 +421,7 @@
alarmData =
AlarmData(
if (nextAlarmMillis > 0) nextAlarmMillis else null,
- SysuiR.string::status_bar_alarm.name
+ SysuiR.string::status_bar_alarm.name,
)
.also { data ->
mainExecutor.execute { clock?.run { events.onAlarmDataChanged(data) } }
@@ -417,6 +429,24 @@
}
}
+ private fun handleZenMode(zen: Int) {
+ val mode = ZenMode.fromInt(zen)
+ if (mode == null) {
+ Log.e(TAG, "Failed to get zen mode from int: $zen")
+ return
+ }
+
+ zenData =
+ ZenData(
+ mode,
+ if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name
+ else SysuiR.string::dnd_is_on.name,
+ )
+ .also { data ->
+ mainExecutor.execute { clock?.run { events.onZenDataChanged(data) } }
+ }
+ }
+
fun registerListeners(parent: View) {
if (isRegistered) {
return
@@ -424,7 +454,7 @@
isRegistered = true
broadcastDispatcher.registerReceiver(
localeBroadcastReceiver,
- IntentFilter(Intent.ACTION_LOCALE_CHANGED)
+ IntentFilter(Intent.ACTION_LOCALE_CHANGED),
)
configurationController.addCallback(configListener)
batteryController.addCallback(batteryCallback)
@@ -434,6 +464,9 @@
parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
+ if (ModesUi.isEnabled) {
+ listenForDnd(this)
+ }
if (MigrateClocksToBlueprint.isEnabled) {
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
@@ -449,7 +482,9 @@
bgExecutor.execute {
// Query ZenMode data
- zenModeCallback.onZenChanged(zenModeController.zen)
+ if (!ModesUi.isEnabled) {
+ zenModeCallback.onZenChanged(zenModeController.zen)
+ }
zenModeCallback.onNextAlarmChanged()
}
}
@@ -605,10 +640,9 @@
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch {
- combine(
- keyguardInteractor.dozeAmount,
- keyguardInteractor.isDozing,
- ) { localDozeAmount, localIsDozing ->
+ combine(keyguardInteractor.dozeAmount, keyguardInteractor.isDozing) {
+ localDozeAmount,
+ localIsDozing ->
localDozeAmount > dozeAmount || localIsDozing
}
.collect { localIsDozing -> isDozing = localIsDozing }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index a94ef36..c7b707d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -17,11 +17,16 @@
import android.content.BroadcastReceiver
import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import android.view.View
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_ACTIVE
+import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.Flags
@@ -36,6 +41,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.plugins.clocks.ClockAnimations
@@ -46,9 +52,15 @@
import com.android.systemui.plugins.clocks.ClockFaceEvents
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockTickRate
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -57,9 +69,12 @@
import com.android.systemui.util.mockito.mock
import java.util.TimeZone
import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -73,15 +88,26 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-import com.android.systemui.Flags as AConfigFlags
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@RunWith(AndroidJUnit4::class)
@SmallTest
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class ClockEventControllerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+ private val testScope = kosmos.testScope
+
@JvmField @Rule val mockito = MockitoJUnit.rule()
+
+ private val mainExecutor = ImmediateExecutor()
+ private lateinit var repository: FakeKeyguardRepository
+ private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
+ private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
+ private lateinit var underTest: ClockEventController
+
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -89,7 +115,6 @@
@Mock private lateinit var animations: ClockAnimations
@Mock private lateinit var events: ClockEvents
@Mock private lateinit var clock: ClockController
- @Mock private lateinit var mainExecutor: DelayableExecutor
@Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var smallClockController: ClockFaceController
@Mock private lateinit var smallClockView: View
@@ -102,12 +127,10 @@
@Mock private lateinit var smallClockEvents: ClockFaceEvents
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
- private lateinit var repository: FakeKeyguardRepository
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
- private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
- private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
- private lateinit var underTest: ClockEventController
+
@Mock private lateinit var zenModeController: ZenModeController
+ private var zenModeControllerCallback: ZenModeController.Callback? = null
@Before
fun setUp() {
@@ -129,12 +152,11 @@
whenever(largeClockController.config)
.thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
+ zenModeRepository.addMode(MANUAL_DND_INACTIVE)
+
repository = FakeKeyguardRepository()
- val withDeps =
- KeyguardInteractorFactory.create(
- repository = repository,
- )
+ val withDeps = KeyguardInteractorFactory.create(repository = repository)
withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) }
underTest =
@@ -151,7 +173,8 @@
bgExecutor,
clockBuffers,
withDeps.featureFlags,
- zenModeController
+ zenModeController,
+ kosmos.zenModeInteractor,
)
underTest.clock = clock
@@ -161,6 +184,10 @@
repository.setIsDozing(true)
repository.setDozeAmount(1f)
}
+
+ val zenCallbackCaptor = argumentCaptor<ZenModeController.Callback>()
+ verify(zenModeController).addCallback(zenCallbackCaptor.capture())
+ zenModeControllerCallback = zenCallbackCaptor.value
}
@Test
@@ -349,17 +376,12 @@
fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor
- .transition(Edge.create(to = AOD)))
+ whenever(keyguardTransitionInteractor.transition(Edge.create(to = AOD)))
.thenReturn(transitionStep)
val job = underTest.listenForAnyStateToAodTransition(this)
transitionStep.value =
- TransitionStep(
- from = GONE,
- to = AOD,
- transitionState = TransitionState.STARTED,
- )
+ TransitionStep(from = GONE, to = AOD, transitionState = TransitionState.STARTED)
yield()
verify(animations, times(2)).doze(1f)
@@ -371,8 +393,7 @@
fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor
- .transition(Edge.create(to = LOCKSCREEN)))
+ whenever(keyguardTransitionInteractor.transition(Edge.create(to = LOCKSCREEN)))
.thenReturn(transitionStep)
val job = underTest.listenForAnyStateToLockscreenTransition(this)
@@ -393,8 +414,7 @@
fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor
- .transition(Edge.create(to = AOD)))
+ whenever(keyguardTransitionInteractor.transition(Edge.create(to = AOD)))
.thenReturn(transitionStep)
val job = underTest.listenForAnyStateToAodTransition(this)
@@ -415,8 +435,7 @@
fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor
- .transition(Edge.create(to = LOCKSCREEN)))
+ whenever(keyguardTransitionInteractor.transition(Edge.create(to = LOCKSCREEN)))
.thenReturn(transitionStep)
val job = underTest.listenForAnyStateToLockscreenTransition(this)
@@ -437,8 +456,7 @@
fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor
- .transition(Edge.create(to = DOZING)))
+ whenever(keyguardTransitionInteractor.transition(Edge.create(to = DOZING)))
.thenReturn(transitionStep)
val job = underTest.listenForAnyStateToDozingTransition(this)
@@ -498,7 +516,57 @@
verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
+ fun listenForDnd_onDndChange_updatesClockZenMode() =
+ testScope.runTest {
+ underTest.listenForDnd(testScope.backgroundScope)
+
+ zenModeRepository.replaceMode(MANUAL_DND_INACTIVE.id, MANUAL_DND_ACTIVE)
+ runCurrent()
+
+ verify(events)
+ .onZenDataChanged(
+ eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
+ )
+
+ zenModeRepository.replaceMode(MANUAL_DND_ACTIVE.id, MANUAL_DND_INACTIVE)
+ runCurrent()
+
+ verify(events).onZenDataChanged(eq(ZenData(ZenMode.OFF, R.string::dnd_is_off.name)))
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_MODES_UI)
+ fun zenModeControllerCallback_onDndChange_updatesClockZenMode() =
+ runBlocking(IMMEDIATE) {
+ zenModeControllerCallback!!.onZenChanged(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ )
+
+ verify(events)
+ .onZenDataChanged(
+ eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
+ )
+
+ zenModeControllerCallback!!.onZenChanged(Settings.Global.ZEN_MODE_OFF)
+
+ verify(events).onZenDataChanged(eq(ZenData(ZenMode.OFF, R.string::dnd_is_off.name)))
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
}
+
+private class ImmediateExecutor : DelayableExecutor {
+ override fun execute(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun executeDelayed(runnable: Runnable, delay: Long, unit: TimeUnit) =
+ runnable.apply { run() }
+
+ override fun executeAtTime(runnable: Runnable, uptimeMillis: Long, unit: TimeUnit) =
+ runnable.apply { run() }
+}