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

Commit 4e434987 authored by amehfooz's avatar amehfooz Committed by Ahmed Mehfooz
Browse files

[SB][ComposeIcons] Add Hotspot Icon

Test: HotspotIconViewModelTest
Bug: 417847376
Flag: com.android.systemui.status_bar_system_status_icons_in_compose
Change-Id: Ic34f99ff9f9c69139e7e3474e503b4ebbd2ff2ac
parent 5ae9c127
Loading
Loading
Loading
Loading
+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),
            )
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -45,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.fakeHotspotController
import com.android.systemui.statusbar.policy.fakeNextAlarmController
import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose
import com.android.systemui.statusbar.systemstatusicons.data.repository.statusBarConfigIconSlotNames
@@ -74,6 +75,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
    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
@@ -87,6 +89,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        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)
@@ -190,6 +193,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
            showNextAlarm()
            showEthernet()
            showVibrate()
            showHotspot()

            assertThat(underTest.activeSlotNames)
                .containsExactly(
@@ -197,6 +201,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotEthernet,
                    slotHotspot,
                    slotNextAlarm,
                    slotVibrate,
                    slotZen,
@@ -213,6 +218,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
                    slotAirplane,
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotHotspot,
                    slotMute,
                    slotNextAlarm,
                    slotWifi,
@@ -294,4 +300,8 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        fakeZenModeRepository.clearModes()
        fakeZenModeRepository.addMode(mode)
    }

    private fun Kosmos.showHotspot() {
        fakeHotspotController.isHotspotEnabled = true
    }
}
+76 −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.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.HotspotStatusInteractor
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 hotspot system status icon. Emits a hotspot icon when hotspot is enabled. Null
 * icon otherwise.
 */
class HotspotIconViewModel
@AssistedInject
constructor(@Assisted context: Context, interactor: HotspotStatusInteractor) :
    SystemStatusIconViewModel, ExclusiveActivatable() {
    init {
        SystemStatusIconsInCompose.expectInNewMode()
    }

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

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

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

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

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

    @AssistedFactory
    interface Factory {
        fun create(context: Context): HotspotIconViewModel
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.systemstatusicons.bluetooth.ui.viewmodel.B
import com.android.systemui.statusbar.systemstatusicons.connecteddisplay.ui.viewmodel.ConnectedDisplayIconViewModel
import com.android.systemui.statusbar.systemstatusicons.domain.interactor.OrderedIconSlotNamesInteractor
import com.android.systemui.statusbar.systemstatusicons.ethernet.ui.viewmodel.EthernetIconViewModel
import com.android.systemui.statusbar.systemstatusicons.hotspot.ui.viewmodel.HotspotIconViewModel
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.wifi.ui.viewmodel.WifiIconViewModel
@@ -55,6 +56,7 @@ constructor(
    bluetoothIconViewModelFactory: BluetoothIconViewModel.Factory,
    connectedDisplayIconViewModelFactory: ConnectedDisplayIconViewModel.Factory,
    ethernetIconViewModelFactory: EthernetIconViewModel.Factory,
    hotspotIconViewModelFactory: HotspotIconViewModel.Factory,
    muteIconViewModelFactory: MuteIconViewModel.Factory,
    nextAlarmIconViewModelFactory: NextAlarmIconViewModel.Factory,
    vibrateIconViewModelFactory: VibrateIconViewModel.Factory,
@@ -74,6 +76,7 @@ constructor(
        connectedDisplayIconViewModelFactory.create(context)
    }
    private val ethernetIcon by lazy { ethernetIconViewModelFactory.create(context) }
    private val hotspotIcon by lazy { hotspotIconViewModelFactory.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) }
@@ -86,6 +89,7 @@ constructor(
            bluetoothIcon,
            connectedDisplayIcon,
            ethernetIcon,
            hotspotIcon,
            muteIcon,
            nextAlarmIcon,
            vibrateIcon,
@@ -120,6 +124,7 @@ constructor(
            launch { bluetoothIcon.activate() }
            launch { connectedDisplayIcon.activate() }
            launch { ethernetIcon.activate() }
            launch { hotspotIcon.activate() }
            launch { muteIcon.activate() }
            launch { nextAlarmIcon.activate() }
            launch { vibrateIcon.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.hotspot.ui.viewmodel

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

val Kosmos.hotspotIconViewModelFactory: HotspotIconViewModel.Factory by
    Kosmos.Fixture {
        object : HotspotIconViewModel.Factory {
            override fun create(context: Context): HotspotIconViewModel =
                HotspotIconViewModel(context, hotspotStatusInteractor)
        }
    }
Loading