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

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

Merge "[SB][ComposeIcons] Add DataSaver Icon" into main

parents b1640c13 090cd993
Loading
Loading
Loading
Loading
+91 −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.datasaver.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
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.fakeDataSaverController // For controlling the fake
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)
@android.platform.test.annotations.EnabledOnRavenwood
class DataSaverIconViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()

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

    @Test
    fun visible_isFalse_byDefault() = kosmos.runTest { assertThat(underTest.visible).isFalse() }

    @Test
    fun visible_dataSaverTurnsOn_isTrue() =
        kosmos.runTest {
            fakeDataSaverController.setDataSaverEnabled(true)

            assertThat(underTest.visible).isTrue()
        }

    @Test
    fun visible_updatesWhenDataSaverStateChanges() =
        kosmos.runTest {
            assertThat(underTest.visible).isFalse()

            fakeDataSaverController.setDataSaverEnabled(true)
            assertThat(underTest.visible).isTrue()

            fakeDataSaverController.setDataSaverEnabled(false)
            assertThat(underTest.visible).isFalse()
        }

    @Test
    fun icon_visible_isCorrect() =
        kosmos.runTest {
            fakeDataSaverController.setDataSaverEnabled(true)
            assertThat(underTest.icon).isEqualTo(expectedDataSaverIcon)
        }

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

    companion object {
        private val expectedDataSaverIcon =
            Icon.Resource(
                res = R.drawable.ic_data_saver,
                contentDescription =
                    ContentDescription.Resource(R.string.accessibility_data_saver_on),
            )
    }
}
+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.fakeDataSaverController
import com.android.systemui.statusbar.policy.fakeHotspotController
import com.android.systemui.statusbar.policy.fakeNextAlarmController
import com.android.systemui.statusbar.policy.vpn.data.repository.vpnRepository
@@ -76,6 +77,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
    private lateinit var slotAirplane: String
    private lateinit var slotBluetooth: String
    private lateinit var slotConnectedDisplay: String
    private lateinit var slotDataSaver: String
    private lateinit var slotEthernet: String
    private lateinit var slotHotspot: String
    private lateinit var slotMute: String
@@ -91,6 +93,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        slotBluetooth = context.getString(com.android.internal.R.string.status_bar_bluetooth)
        slotConnectedDisplay =
            context.getString(com.android.internal.R.string.status_bar_connected_display)
        slotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver)
        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)
@@ -193,6 +196,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
            showZenMode()
            showBluetooth()
            showConnectedDisplay()
            showDataSaver()
            showAirplaneMode()
            showNextAlarm()
            showEthernet()
@@ -205,6 +209,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
                    slotAirplane,
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotDataSaver,
                    slotEthernet,
                    slotHotspot,
                    slotNextAlarm,
@@ -224,6 +229,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
                    slotAirplane,
                    slotBluetooth,
                    slotConnectedDisplay,
                    slotDataSaver,
                    slotHotspot,
                    slotMute,
                    slotNextAlarm,
@@ -312,6 +318,10 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() {
        fakeHotspotController.isHotspotEnabled = true
    }

    private fun Kosmos.showDataSaver() {
        fakeDataSaverController.setDataSaverEnabled(true)
    }

    private fun Kosmos.showVpn() {
        vpnRepository.vpnState.value = VpnState(isEnabled = true)
    }
+78 −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.datasaver.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.DataSaverStatusInteractor
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

/**
 * View model for the data saver system status icon. Emits a data saver icon when data saver is
 * enabled. Null icon otherwise.
 */
class DataSaverIconViewModel
@AssistedInject
constructor(@Assisted context: Context, interactor: DataSaverStatusInteractor) :
    SystemStatusIconViewModel.Default, ExclusiveActivatable() {
    init {
        SystemStatusIconsInCompose.expectInNewMode()
    }

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

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

    override val visible: Boolean by
        hydrator.hydratedStateOf(
            traceName = "SystemStatus.dataSaverVisible",
            initialValue = false,
            source = interactor.isEnabled,
        )

    override val icon: Icon?
        get() = visible.toUiState()

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

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

    @AssistedFactory
    interface Factory {
        fun create(context: Context): DataSaverIconViewModel
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.systemui.statusbar.systemstatusicons.airplane.ui.viewmodel.Ai
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.datasaver.ui.viewmodel.DataSaverIconViewModel
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
@@ -55,6 +56,7 @@ constructor(
    airplaneModeIconViewModelFactory: AirplaneModeIconViewModel.Factory,
    bluetoothIconViewModelFactory: BluetoothIconViewModel.Factory,
    connectedDisplayIconViewModelFactory: ConnectedDisplayIconViewModel.Factory,
    dataSaverIconViewModelFactory: DataSaverIconViewModel.Factory,
    ethernetIconViewModelFactory: EthernetIconViewModel.Factory,
    hotspotIconViewModelFactory: HotspotIconViewModel.Factory,
    muteIconViewModelFactory: MuteIconViewModel.Factory,
@@ -76,6 +78,7 @@ constructor(
    private val connectedDisplayIcon by lazy {
        connectedDisplayIconViewModelFactory.create(context)
    }
    private val dataSaverIcon by lazy { dataSaverIconViewModelFactory.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) }
@@ -90,6 +93,7 @@ constructor(
            airplaneModeIcon,
            bluetoothIcon,
            connectedDisplayIcon,
            dataSaverIcon,
            ethernetIcon,
            hotspotIcon,
            muteIcon,
@@ -122,6 +126,7 @@ constructor(
            launch { airplaneModeIcon.activate() }
            launch { bluetoothIcon.activate() }
            launch { connectedDisplayIcon.activate() }
            launch { dataSaverIcon.activate() }
            launch { ethernetIcon.activate() }
            launch { hotspotIcon.activate() }
            launch { muteIcon.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.datasaver.ui.viewmodel

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

val Kosmos.dataSaverIconViewModelFactory: DataSaverIconViewModel.Factory by
    Kosmos.Fixture {
        object : DataSaverIconViewModel.Factory {
            override fun create(context: Context): DataSaverIconViewModel =
                DataSaverIconViewModel(context, dataSaverStatusInteractor)
        }
    }
Loading