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