Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5ae9c127 authored by amehfooz's avatar amehfooz Committed by Ahmed Mehfooz
Browse files

[SB][ComposeIcons] Add Alarm Icon

Test: AlarmIconViewModelTest
Test: NextAlarmInteractorTest
Bug: 417559762
Flag: com.android.systemui.status_bar_system_status_icons_in_compose
Change-Id: Ie9b112fb9f809c8ca08df5ccfedc3db32cc6288c
parent 4f07b4ea
Loading
Loading
Loading
Loading
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.systemui.statusbar.systemstatusicons.alarm.ui.viewmodel

import android.app.AlarmManager
import android.app.PendingIntent
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.fakeNextAlarmController
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(SystemStatusIconsInCompose.FLAG_NAME)
class NextAlarmIconViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope

    private val underTest: NextAlarmIconViewModel =
        kosmos.nextAlarmIconViewModelFactory.create(context).apply { activateIn(testScope) }

    @Test
    fun icon_alarmNotSet_outputsNull() =
        kosmos.runTest {
            fakeNextAlarmController.setNextAlarm(null)
            assertThat(underTest.icon).isNull()
        }

    @Test
    fun icon_alarmSet_outputsIcon() =
        kosmos.runTest {
            val alarmClockInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>())
            fakeNextAlarmController.setNextAlarm(alarmClockInfo)

            assertThat(underTest.icon).isEqualTo(EXPECTED_ALARM_ICON)
        }

    @Test
    fun icon_updatesWhenAlarmChanges() =
        kosmos.runTest {
            assertThat(underTest.icon).isNull()

            val alarmInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>())
            fakeNextAlarmController.setNextAlarm(alarmInfo)

            assertThat(underTest.icon).isEqualTo(EXPECTED_ALARM_ICON)

            fakeNextAlarmController.setNextAlarm(null)
            assertThat(underTest.icon).isNull()
        }

    companion object {
        private val EXPECTED_ALARM_ICON =
            Icon.Resource(
                res = R.drawable.ic_alarm,
                contentDescription = ContentDescription.Resource(R.string.status_bar_alarm),
            )
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -15,7 +15,9 @@

package com.android.systemui.statusbar.systemstatusicons.ui.viewmodel

import android.app.AlarmManager
import android.app.AutomaticZenRule
import android.app.PendingIntent
import android.bluetooth.BluetoothProfile
import android.content.testableContext
import android.media.AudioManager
@@ -43,6 +45,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepo
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.policy.bluetooth.data.repository.bluetoothRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.policy.fakeNextAlarmController
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.statusbar.systemstatusicons.data.repository.statusBarConfigIconSlotNames
import com.android.systemui.testKosmos
@@ -72,6 +75,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
    private lateinit var slotConnectedDisplay: String
    private lateinit var slotEthernet: String
    private lateinit var slotMute: String
    private lateinit var slotNextAlarm: String
    private lateinit var slotVibrate: String
    private lateinit var slotWifi: String
    private lateinit var slotZen: String
@@ -84,6 +88,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
            context.getString(com.android.internal.R.string.status_bar_connected_display)
        slotEthernet = context.getString(com.android.internal.R.string.status_bar_ethernet)
        slotMute = context.getString(com.android.internal.R.string.status_bar_mute)
        slotNextAlarm = context.getString(com.android.internal.R.string.status_bar_alarm_clock)
        slotVibrate = context.getString(com.android.internal.R.string.status_bar_volume)
        slotWifi = context.getString(com.android.internal.R.string.status_bar_wifi)
        slotZen = context.getString(com.android.internal.R.string.status_bar_zen)
@@ -182,6 +187,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
            showBluetooth()
            showConnectedDisplay()
            showAirplaneMode()
            showNextAlarm()
            showEthernet()
            showVibrate()

@@ -191,6 +197,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotEthernet,
                    slotNextAlarm,
                    slotVibrate,
                    slotZen,
                )
@@ -207,6 +214,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotMute,
                    slotNextAlarm,
                    slotWifi,
                    slotZen,
                )
@@ -256,6 +264,11 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        fakeAudioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_SILENT))
    }

    private fun Kosmos.showNextAlarm() {
        val alarmClockInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>())
        fakeNextAlarmController.setNextAlarm(alarmClockInfo)
    }

    private fun Kosmos.showVibrate() {
        fakeAudioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
    }
+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.systemui.statusbar.systemstatusicons.alarm.ui.viewmodel

import android.content.Context
import androidx.compose.runtime.getValue
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.NextAlarmInteractor
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.statusbar.systemstatusicons.ui.viewmodel.SystemStatusIconViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map

/**
 * View model for the next alarm system status icon. Emits an alarm clock icon when an alarm is set
 * and null icon otherwise.
 */
class NextAlarmIconViewModel
@AssistedInject
constructor(@Assisted context: Context, interactor: NextAlarmInteractor) :
    SystemStatusIconViewModel, ExclusiveActivatable() {
    init {
        SystemStatusIconsInCompose.expectInNewMode()
    }

    private val hydrator = Hydrator("NextAlarmIconViewModel.hydrator")

    override val slotName = context.getString(com.android.internal.R.string.status_bar_alarm_clock)

    override val icon: Icon? by
        hydrator.hydratedStateOf(
            traceName = null,
            initialValue = null,
            source = interactor.isAlarmSet.map { it.toUiState() },
        )

    override suspend fun onActivated(): Nothing {
        hydrator.activate()
    }

    private fun Boolean.toUiState(): Icon? =
        if (this) {
            Icon.Resource(
                res = R.drawable.ic_alarm,
                contentDescription = ContentDescription.Resource(R.string.status_bar_alarm),
            )
        } else {
            null
        }

    @AssistedFactory
    interface Factory {
        fun create(context: Context): NextAlarmIconViewModel
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.statusbar.systemstatusicons.airplane.ui.viewmodel.AirplaneModeIconViewModel
import com.android.systemui.statusbar.systemstatusicons.alarm.ui.viewmodel.NextAlarmIconViewModel
import com.android.systemui.statusbar.systemstatusicons.bluetooth.ui.viewmodel.BluetoothIconViewModel
import com.android.systemui.statusbar.systemstatusicons.connecteddisplay.ui.viewmodel.ConnectedDisplayIconViewModel
import com.android.systemui.statusbar.systemstatusicons.domain.interactor.OrderedIconSlotNamesInteractor
@@ -55,6 +56,7 @@ constructor(
    connectedDisplayIconViewModelFactory: ConnectedDisplayIconViewModel.Factory,
    ethernetIconViewModelFactory: EthernetIconViewModel.Factory,
    muteIconViewModelFactory: MuteIconViewModel.Factory,
    nextAlarmIconViewModelFactory: NextAlarmIconViewModel.Factory,
    vibrateIconViewModelFactory: VibrateIconViewModel.Factory,
    wifiIconViewModelFactory: WifiIconViewModel.Factory,
    zenModeIconViewModelFactory: ZenModeIconViewModel.Factory,
@@ -73,6 +75,7 @@ constructor(
    }
    private val ethernetIcon by lazy { ethernetIconViewModelFactory.create(context) }
    private val muteIcon by lazy { muteIconViewModelFactory.create(context) }
    private val nextAlarmIcon by lazy { nextAlarmIconViewModelFactory.create(context) }
    private val vibrateIcon by lazy { vibrateIconViewModelFactory.create(context) }
    private val wifiIcon by lazy { wifiIconViewModelFactory.create(context) }
    private val zenModeIcon by lazy { zenModeIconViewModelFactory.create(context) }
@@ -84,6 +87,7 @@ constructor(
            connectedDisplayIcon,
            ethernetIcon,
            muteIcon,
            nextAlarmIcon,
            vibrateIcon,
            wifiIcon,
            zenModeIcon,
@@ -117,6 +121,7 @@ constructor(
            launch { connectedDisplayIcon.activate() }
            launch { ethernetIcon.activate() }
            launch { muteIcon.activate() }
            launch { nextAlarmIcon.activate() }
            launch { vibrateIcon.activate() }
            launch { wifiIcon.activate() }
            launch { zenModeIcon.activate() }
+29 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.systemui.statusbar.systemstatusicons.alarm.ui.viewmodel

import android.content.Context
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.policy.domain.interactor.nextAlarmInteractor

val Kosmos.nextAlarmIconViewModelFactory: NextAlarmIconViewModel.Factory by
    Kosmos.Fixture {
        object : NextAlarmIconViewModel.Factory {
            override fun create(context: Context): NextAlarmIconViewModel =
                NextAlarmIconViewModel(context, nextAlarmInteractor)
        }
    }
Loading