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

Commit 584dfecc authored by Ahmed Mehfooz's avatar Ahmed Mehfooz Committed by Android (Google) Code Review
Browse files

Merge changes Ic34f99ff,Ie9b112fb,I9b59244c,Ie578cc22 into main

* changes:
  [SB][ComposeIcons] Add Hotspot Icon
  [SB][ComposeIcons] Add Alarm Icon
  [SB][ComposeIcons] Add Connected Display Icon
   [SB][ComposeIcons] Add Wifi Icon
parents 1b2b5e21 4e434987
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),
            )
    }
}
+109 −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.connecteddisplay.ui.viewmodel

import android.content.testableContext
import android.platform.test.annotations.EnableFlags
import android.view.Display.FLAG_SECURE
import android.view.Display.TYPE_EXTERNAL
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.display.data.repository.display
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
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.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

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

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val keyguardRepository = kosmos.fakeKeyguardRepository

    private val underTest =
        kosmos.connectedDisplayIconViewModelFactory.create(kosmos.testableContext).apply {
            activateIn(kosmos.testScope)
        }

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

    @Test
    fun icon_displayConnected_outputsIcon() =
        kosmos.runTest {
            keyguardRepository.setKeyguardShowing(true)
            displayRepository.setDefaultDisplayOff(false)
            displayRepository.addDisplay(display(type = TYPE_EXTERNAL, id = 1))

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

    @Test
    fun icon_displayConnectedSecure_outputsIcon() =
        kosmos.runTest {
            keyguardRepository.setKeyguardShowing(false)
            displayRepository.setDefaultDisplayOff(false)
            displayRepository.addDisplay(display(type = TYPE_EXTERNAL, flags = FLAG_SECURE, id = 1))

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

    @Test
    fun icon_updatesWhenDisplayConnectionChanges() =
        kosmos.runTest {
            displayRepository.setDefaultDisplayOff(false)
            assertThat(underTest.icon).isNull()

            keyguardRepository.setKeyguardShowing(true)
            displayRepository.addDisplay(display(type = TYPE_EXTERNAL, id = 1))

            assertThat(underTest.icon).isEqualTo(expectedConnectedDisplayIcon)

            displayRepository.removeDisplay(1)
            assertThat(underTest.icon).isNull()

            keyguardRepository.setKeyguardShowing(false)
            displayRepository.addDisplay(display(type = TYPE_EXTERNAL, flags = FLAG_SECURE, id = 2))
            assertThat(underTest.icon).isEqualTo(expectedConnectedDisplayIcon)

            displayRepository.removeDisplay(2)
            assertThat(underTest.icon).isNull()
        }

    companion object {
        private val expectedConnectedDisplayIcon =
            Icon.Resource(
                res = R.drawable.stat_sys_connected_display,
                contentDescription =
                    ContentDescription.Resource(R.string.connected_display_icon_desc),
            )
    }
}
+82 −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.hotspot.ui.viewmodel

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.fakeHotspotController
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

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

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val fakeController = kosmos.fakeHotspotController
    private val underTest =
        kosmos.hotspotIconViewModelFactory.create(context).apply { activateIn(kosmos.testScope) }

    @Test
    fun icon_hotspotDisabled_outputsNull() =
        kosmos.runTest {
            fakeController.isHotspotEnabled = false
            assertThat(underTest.icon).isNull()
        }

    @Test
    fun icon_hotspotEnabled_outputsIcon() =
        kosmos.runTest {
            fakeController.isHotspotEnabled = true
            assertThat(underTest.icon).isEqualTo(EXPECTED_HOTSPOT_ICON)
        }

    @Test
    fun icon_updatesWhenHotspotStateChanges() =
        kosmos.runTest {
            fakeController.isHotspotEnabled = false
            assertThat(underTest.icon).isNull()

            fakeController.isHotspotEnabled = true
            assertThat(underTest.icon).isEqualTo(EXPECTED_HOTSPOT_ICON)

            fakeController.isHotspotEnabled = false
            assertThat(underTest.icon).isNull()
        }

    companion object {
        private val EXPECTED_HOTSPOT_ICON =
            Icon.Resource(
                res = R.drawable.ic_hotspot,
                contentDescription =
                    ContentDescription.Resource(R.string.accessibility_status_bar_hotspot),
            )
    }
}
+76 −5
Original line number Diff line number Diff line
@@ -15,17 +15,23 @@

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
import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.display
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
@@ -35,8 +41,12 @@ import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.pipeline.airplane.data.repository.airplaneModeRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
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.fakeHotspotController
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
@@ -63,18 +73,27 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {

    private lateinit var slotAirplane: String
    private lateinit var slotBluetooth: String
    private lateinit var slotConnectedDisplay: String
    private lateinit var slotEthernet: String
    private lateinit var slotHotspot: 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

    @Before
    fun setUp() {
        slotAirplane = context.getString(com.android.internal.R.string.status_bar_airplane)
        slotBluetooth = context.getString(com.android.internal.R.string.status_bar_bluetooth)
        slotConnectedDisplay =
            context.getString(com.android.internal.R.string.status_bar_connected_display)
        slotEthernet = context.getString(com.android.internal.R.string.status_bar_ethernet)
        slotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot)
        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)
    }

@@ -169,20 +188,42 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {

            showZenMode()
            showBluetooth()
            showConnectedDisplay()
            showAirplaneMode()
            showNextAlarm()
            showEthernet()
            showVibrate()
            showHotspot()

            assertThat(underTest.activeSlotNames)
                .containsExactly(slotAirplane, slotBluetooth, slotEthernet, slotVibrate, slotZen)
                .containsExactly(
                    slotAirplane,
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotEthernet,
                    slotHotspot,
                    slotNextAlarm,
                    slotVibrate,
                    slotZen,
                )
                .inOrder()

            // The mute and vibrate icon can not be shown at the same time so we have to test it
            // separately.
            showMute()
            // The [mute,vibrate] and [ethernet, wifi] icons can not be shown at the same time so we
            // have to test it separately.
            showMute() // This will make vibrate inactive
            showWifi() // This will make ethernet inactive

            assertThat(underTest.activeSlotNames)
                .containsExactly(slotAirplane, slotBluetooth, slotEthernet, slotMute, slotZen)
                .containsExactly(
                    slotAirplane,
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotHotspot,
                    slotMute,
                    slotNextAlarm,
                    slotWifi,
                    slotZen,
                )
                .inOrder()
        }

@@ -208,6 +249,19 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        )
    }

    private suspend fun Kosmos.showConnectedDisplay(isSecure: Boolean = false) {
        fakeKeyguardRepository.setKeyguardShowing(!isSecure)
        displayRepository.setDefaultDisplayOff(false)
        val flags = if (isSecure) Display.FLAG_SECURE else 0
        displayRepository.addDisplay(
            display(
                type = Display.TYPE_EXTERNAL,
                flags = flags,
                id = (displayRepository.displays.value.maxOfOrNull { it.displayId } ?: 0) + 1,
            )
        )
    }

    private fun Kosmos.showEthernet() {
        connectivityRepository.fake.setEthernetConnected(default = true, validated = true)
    }
@@ -216,10 +270,23 @@ 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))
    }

    private fun Kosmos.showWifi() {
        fakeWifiRepository.setIsWifiEnabled(true)
        val testNetwork =
            WifiNetworkModel.Active.of(isValidated = true, level = 4, ssid = "TestWifi")
        fakeWifiRepository.setWifiNetwork(testNetwork)
        connectivityRepository.fake.setWifiConnected()
    }

    private fun Kosmos.showZenMode() {
        val modeId = "zenModeTestRule"
        val modeName = "Test Zen Mode"
@@ -233,4 +300,8 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        fakeZenModeRepository.clearModes()
        fakeZenModeRepository.addMode(mode)
    }

    private fun Kosmos.showHotspot() {
        fakeHotspotController.isHotspotEnabled = true
    }
}
+143 −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.wifi.ui.viewmodel

import android.content.testableContext
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.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon as CommonIcon
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.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon as PipelineWifiIcon
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@EnableFlags(SystemStatusIconsInCompose.FLAG_NAME, NewStatusBarIcons.FLAG_NAME)
@SmallTest
@RunWith(AndroidJUnit4::class)
class WifiIconViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val underTest = kosmos.wifiIconViewModelFactory.create(kosmos.testableContext)

    @Before
    fun setUp() {
        underTest.activateIn(kosmos.testScope)
    }

    @Test
    fun icon_wifiDisabled_IsNull() =
        kosmos.runTest {
            fakeWifiRepository.setIsWifiEnabled(false)

            assertThat(underTest.icon).isNull()
        }

    @Test
    fun icon_wifiEnabled_isDefault_isShown() =
        kosmos.runTest {
            fakeWifiRepository.setIsWifiEnabled(true)
            val testNetwork =
                WifiNetworkModel.Active.of(isValidated = true, level = 4, ssid = "TestWifi")
            fakeWifiRepository.setWifiNetwork(testNetwork)
            connectivityRepository.fake.setWifiConnected()

            val icon = underTest.icon
            assertThat(icon).isNotNull()
            assertThat(icon).isInstanceOf(CommonIcon.Resource::class.java)
            val resourceIcon = icon as CommonIcon.Resource

            val expectedPipelineIcon =
                PipelineWifiIcon.fromModel(testNetwork, context, showHotspotInfo = false)
                    as PipelineWifiIcon.Visible
            assertThat(resourceIcon.res).isEqualTo(expectedPipelineIcon.icon.res)
            assertThat(resourceIcon.contentDescription?.loadContentDescription(context))
                .isEqualTo(
                    expectedPipelineIcon.icon.contentDescription?.loadContentDescription(context)
                )
        }

    @Test
    fun icon_wifiEnabled_inactiveNetwork_isShownAsNoNetwork() =
        kosmos.runTest {
            fakeWifiRepository.setIsWifiEnabled(true)
            fakeWifiRepository.setWifiNetwork(WifiNetworkModel.Inactive()) // Main condition
            connectivityRepository.fake.setWifiConnected() // Sets isWifiDefault to true

            val icon = underTest.icon
            assertThat(icon).isNotNull()
            assertThat(icon).isInstanceOf(CommonIcon.Resource::class.java)
            val resourceIcon = icon as CommonIcon.Resource

            val expectedPipelineIcon =
                PipelineWifiIcon.fromModel(
                    WifiNetworkModel.Inactive(),
                    context,
                    showHotspotInfo = false,
                ) as PipelineWifiIcon.Visible
            assertThat(resourceIcon.res).isEqualTo(expectedPipelineIcon.icon.res)
            assertThat(resourceIcon.contentDescription?.loadContentDescription(context))
                .isEqualTo(
                    expectedPipelineIcon.icon.contentDescription?.loadContentDescription(context)
                )
        }

    @Test
    fun icon_isForceHidden_isNull() =
        kosmos.runTest {
            fakeWifiRepository.setIsWifiEnabled(true)
            fakeWifiRepository.setWifiNetwork(
                WifiNetworkModel.Active.of(isValidated = true, level = 4, ssid = "TestWifi")
            )
            connectivityRepository.fake.setWifiConnected()
            connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))

            assertThat(underTest.icon).isNull()
        }

    @Test
    fun icon_wifiNetworkIsCarrierMerged_isNull() =
        kosmos.runTest {
            fakeWifiRepository.setIsWifiEnabled(true)
            connectivityRepository.fake.setWifiConnected()

            fakeWifiRepository.setWifiNetwork(
                WifiNetworkModel.CarrierMerged.of(
                    subscriptionId = 1212,
                    level = 4,
                    numberOfLevels = 4,
                )
            )

            assertThat(underTest.icon).isNull()
        }
}
Loading