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

Commit 0401a56f authored by amehfooz's avatar amehfooz Committed by Ahmed Mehfooz
Browse files

[SB][ComposeIcons] Add Wifi Icon

Test: WifiIconViewModelTest
Bug: 416768350
Flag: com.android.systemui.status_bar_system_status_icons_in_compose
Change-Id: Ie578cc2210b665ef57c33b5cc1a85e0e5fcd3393
parent 14f04226
Loading
Loading
Loading
Loading
+18 −3
Original line number Original line Diff line number Diff line
@@ -35,6 +35,8 @@ import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.pipeline.airplane.data.repository.airplaneModeRepository
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.connectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
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.bluetooth.data.repository.bluetoothRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
@@ -66,6 +68,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
    private lateinit var slotEthernet: String
    private lateinit var slotEthernet: String
    private lateinit var slotMute: String
    private lateinit var slotMute: String
    private lateinit var slotVibrate: String
    private lateinit var slotVibrate: String
    private lateinit var slotWifi: String
    private lateinit var slotZen: String
    private lateinit var slotZen: String


    @Before
    @Before
@@ -75,6 +78,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        slotEthernet = context.getString(com.android.internal.R.string.status_bar_ethernet)
        slotEthernet = context.getString(com.android.internal.R.string.status_bar_ethernet)
        slotMute = context.getString(com.android.internal.R.string.status_bar_mute)
        slotMute = context.getString(com.android.internal.R.string.status_bar_mute)
        slotVibrate = context.getString(com.android.internal.R.string.status_bar_volume)
        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)
        slotZen = context.getString(com.android.internal.R.string.status_bar_zen)
    }
    }


@@ -172,17 +176,20 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
            showAirplaneMode()
            showAirplaneMode()
            showEthernet()
            showEthernet()
            showVibrate()
            showVibrate()
            showWifi()


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


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


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


@@ -220,6 +227,14 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        fakeAudioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
        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() {
    private fun Kosmos.showZenMode() {
        val modeId = "zenModeTestRule"
        val modeId = "zenModeTestRule"
        val modeName = "Test Zen Mode"
        val modeName = "Test Zen Mode"
+143 −0
Original line number Original line 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()
        }
}
+15 −15
Original line number Original line Diff line number Diff line
@@ -26,9 +26,9 @@ import java.lang.IllegalArgumentException
 *
 *
 * Must be subclassed for each distinct location.
 * Must be subclassed for each distinct location.
 */
 */
abstract class LocationBasedWifiViewModel(
@Deprecated("Deprecated in favor of WifiIconViewModel once SystemStatusIconsInCompose is launched")
    private val commonImpl: WifiViewModelCommon,
abstract class LocationBasedWifiViewModel(private val commonImpl: WifiViewModelCommon) :
) : WifiViewModelCommon by commonImpl {
    WifiViewModelCommon by commonImpl {
    val defaultColor: Int = Color.WHITE
    val defaultColor: Int = Color.WHITE


    companion object {
    companion object {
@@ -54,24 +54,24 @@ abstract class LocationBasedWifiViewModel(
 * A view model for the wifi icon shown on the "home" page (aka, when the device is unlocked and not
 * A view model for the wifi icon shown on the "home" page (aka, when the device is unlocked and not
 * showing the shade, so the user is on the home-screen, or in an app).
 * showing the shade, so the user is on the home-screen, or in an app).
 */
 */
class HomeWifiViewModel(
@Deprecated("Deprecated in favor of WifiIconViewModel once SystemStatusIconsInCompose is launched")
    commonImpl: WifiViewModelCommon,
class HomeWifiViewModel(commonImpl: WifiViewModelCommon) :
) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
    WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)


/** A view model for the wifi icon shown on keyguard (lockscreen). */
/** A view model for the wifi icon shown on keyguard (lockscreen). */
class KeyguardWifiViewModel(
@Deprecated("Deprecated in favor of WifiIconViewModel once SystemStatusIconsInCompose is launched")
    commonImpl: WifiViewModelCommon,
class KeyguardWifiViewModel(commonImpl: WifiViewModelCommon) :
) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
    WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)


/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
class QsWifiViewModel(
@Deprecated("Deprecated in favor of WifiIconViewModel once SystemStatusIconsInCompose is launched")
    commonImpl: WifiViewModelCommon,
class QsWifiViewModel(commonImpl: WifiViewModelCommon) :
) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
    WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)


/**
/**
 * A view model for the wifi icon in the shade carrier group (visible when quick settings is fully
 * A view model for the wifi icon in the shade carrier group (visible when quick settings is fully
 * expanded, and in large screen shade). Currently unused.
 * expanded, and in large screen shade). Currently unused.
 */
 */
class ShadeCarrierGroupWifiViewModel(
@Deprecated("Deprecated in favor of WifiIconViewModel once SystemStatusIconsInCompose is launched")
    commonImpl: WifiViewModelCommon,
class ShadeCarrierGroupWifiViewModel(commonImpl: WifiViewModelCommon) :
) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
    WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
+13 −1
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.systemstatusicons.domain.interactor.Ordere
import com.android.systemui.statusbar.systemstatusicons.ethernet.ui.viewmodel.EthernetIconViewModel
import com.android.systemui.statusbar.systemstatusicons.ethernet.ui.viewmodel.EthernetIconViewModel
import com.android.systemui.statusbar.systemstatusicons.ringer.ui.viewmodel.MuteIconViewModel
import com.android.systemui.statusbar.systemstatusicons.ringer.ui.viewmodel.MuteIconViewModel
import com.android.systemui.statusbar.systemstatusicons.ringer.ui.viewmodel.VibrateIconViewModel
import com.android.systemui.statusbar.systemstatusicons.ringer.ui.viewmodel.VibrateIconViewModel
import com.android.systemui.statusbar.systemstatusicons.wifi.ui.viewmodel.WifiIconViewModel
import com.android.systemui.statusbar.systemstatusicons.zenmode.ui.viewmodel.ZenModeIconViewModel
import com.android.systemui.statusbar.systemstatusicons.zenmode.ui.viewmodel.ZenModeIconViewModel
import dagger.assisted.Assisted
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedFactory
@@ -53,6 +54,7 @@ constructor(
    ethernetIconViewModelFactory: EthernetIconViewModel.Factory,
    ethernetIconViewModelFactory: EthernetIconViewModel.Factory,
    muteIconViewModelFactory: MuteIconViewModel.Factory,
    muteIconViewModelFactory: MuteIconViewModel.Factory,
    vibrateIconViewModelFactory: VibrateIconViewModel.Factory,
    vibrateIconViewModelFactory: VibrateIconViewModel.Factory,
    wifiIconViewModelFactory: WifiIconViewModel.Factory,
    zenModeIconViewModelFactory: ZenModeIconViewModel.Factory,
    zenModeIconViewModelFactory: ZenModeIconViewModel.Factory,
) : ExclusiveActivatable() {
) : ExclusiveActivatable() {


@@ -67,10 +69,19 @@ constructor(
    private val ethernetIcon by lazy { ethernetIconViewModelFactory.create(context) }
    private val ethernetIcon by lazy { ethernetIconViewModelFactory.create(context) }
    private val muteIcon by lazy { muteIconViewModelFactory.create(context) }
    private val muteIcon by lazy { muteIconViewModelFactory.create(context) }
    private val vibrateIcon by lazy { vibrateIconViewModelFactory.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) }
    private val zenModeIcon by lazy { zenModeIconViewModelFactory.create(context) }


    private val unOrderedIconViewModels: List<SystemStatusIconViewModel> by lazy {
    private val unOrderedIconViewModels: List<SystemStatusIconViewModel> by lazy {
        listOf(airplaneModeIcon, bluetoothIcon, ethernetIcon, muteIcon, vibrateIcon, zenModeIcon)
        listOf(
            airplaneModeIcon,
            bluetoothIcon,
            ethernetIcon,
            muteIcon,
            vibrateIcon,
            wifiIcon,
            zenModeIcon,
        )
    }
    }


    private val viewModelMap: Map<String, SystemStatusIconViewModel> by lazy {
    private val viewModelMap: Map<String, SystemStatusIconViewModel> by lazy {
@@ -99,6 +110,7 @@ constructor(
            launch { ethernetIcon.activate() }
            launch { ethernetIcon.activate() }
            launch { muteIcon.activate() }
            launch { muteIcon.activate() }
            launch { vibrateIcon.activate() }
            launch { vibrateIcon.activate() }
            launch { wifiIcon.activate() }
            launch { zenModeIcon.activate() }
            launch { zenModeIcon.activate() }
        }
        }
        awaitCancellation()
        awaitCancellation()
+73 −0
Original line number Original line 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.Context
import androidx.compose.runtime.getValue
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
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 wifi system status icon. Emits a wifi icon when wifi is enabled and should be
 * shown. This viewModel is active when [SystemStatusIconsInCompose] is enabled and replaces
 * [LocationBasedWifiViewModel].
 */
class WifiIconViewModel
@AssistedInject
constructor(@Assisted private val context: Context, wifiViewModel: WifiViewModel) :
    SystemStatusIconViewModel, ExclusiveActivatable() {

    init {
        SystemStatusIconsInCompose.expectInNewMode()
    }

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

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

    override val icon: Icon? by
        hydrator.hydratedStateOf(
            traceName = "SystemStatus.wifiIcon",
            initialValue = null,
            source = wifiViewModel.wifiIcon.map { it.toSystemStatusIcon() },
        )

    private fun WifiIcon.toSystemStatusIcon(): Icon? {
        return when (this) {
            is WifiIcon.Hidden -> null
            is WifiIcon.Visible -> this.icon
        }
    }

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

    @AssistedFactory
    interface Factory {
        fun create(context: Context): WifiIconViewModel
    }
}
Loading