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

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

[SB][ComposeIcons] Create Composable System Status Icons

This change sets the foundation for system status icons in compose
by doing the following:

Adds a composable to contain system status icons in the statusbar.
Currently, the container is showing up in the middle of the screen
because it is filling the width of status_bar_end_side_container and
the content is aligned to the start.

Adds a SystemStatusIconsViewModel which will consolidate individual
SystemStatusIconViewModels to emit a list of icons to be displayed
in the status bar.

Also, adds a compose version for the airplane mode icon.

Bug: 407813930
Test: Make sure airplane mode icon is updated
Screenshot provided in bug.
Flag: com.android.systemui.status_bar_system_status_icons_in_compose

Change-Id: I6d46eb41e09bc326c501ec9c37c07664ab40c056
parent 2b5a8e70
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -480,6 +480,13 @@ flag {
   bug: "291321279"
}

flag {
  name: "status_bar_system_status_icons_in_compose"
  namespace: "systemui"
  description: "Use compose for displaying icons in the status bar system status area"
  bug: "406922954"
}

flag {
    name: "status_bar_swipe_over_chip"
    namespace: "systemui"
+1 −0
Original line number Diff line number Diff line
@@ -667,6 +667,7 @@ private fun ContentScope.StatusIcons(

    val chipHighlight = viewModel.quickSettingsChipHighlight

    // TODO(408001821): Use composable system status icons here instead.
    AndroidView(
        factory = { context ->
            iconManager.setTint(primaryColor, inverseColor)
+6 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewM
import com.android.systemui.statusbar.pipeline.shared.ui.model.ChipsVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.statusbar.systemstatusicons.ui.viewmodel.SystemStatusIconsViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -72,6 +73,11 @@ class FakeHomeStatusBarViewModel(
        object : BatteryViewModel.Factory {
            override fun create(): BatteryViewModel = mock(BatteryViewModel::class.java)
        }
    override val systemStatusIconsViewModelFactory: SystemStatusIconsViewModel.Factory =
        object : SystemStatusIconsViewModel.Factory {
            override fun create(): SystemStatusIconsViewModel =
                mock(SystemStatusIconsViewModel::class.java)
        }

    override val shouldShowOperatorNameView = MutableStateFlow(false)

+127 −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.airplane.ui.viewmodel

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.pipeline.airplane.data.repository.airplaneModeRepository
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.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class AirplaneModeIconViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val underTest = kosmos.airplaneModeIconViewModelFactory.create()
    private val fakeConnectivityRepository = kosmos.connectivityRepository.fake
    private val expectedAirplaneIcon =
        Icon.Resource(
            res = com.android.internal.R.drawable.ic_qs_airplane,
            contentDescription = ContentDescription.Resource(R.string.accessibility_airplane_mode),
        )

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

    @Test
    fun icon_notAirplaneMode_outputsNull() =
        kosmos.runTest {
            fakeConnectivityRepository.setForceHiddenIcons(setOf())
            airplaneModeRepository.setIsAirplaneMode(false)

            assertThat(underTest.icon).isNull()
        }

    @Test
    fun icon_forceHidden_outputsNull() =
        kosmos.runTest {
            fakeConnectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
            airplaneModeRepository.setIsAirplaneMode(true)

            assertThat(underTest.icon).isNull()
        }

    @Test
    fun icon_isAirplaneModeAndNotForceHidden_outputsIcon() =
        kosmos.runTest {
            fakeConnectivityRepository.setForceHiddenIcons(setOf())
            airplaneModeRepository.setIsAirplaneMode(true)

            val expectedIcon =
                Icon.Resource(
                    res = com.android.internal.R.drawable.ic_qs_airplane,
                    contentDescription =
                        ContentDescription.Resource(R.string.accessibility_airplane_mode),
                )
            assertThat(underTest.icon).isEqualTo(expectedIcon)
        }

    @Test
    fun icon_updatesWhenAirplaneModeChanges() =
        kosmos.runTest {
            fakeConnectivityRepository.setForceHiddenIcons(setOf())

            // Start not in airplane mode
            airplaneModeRepository.setIsAirplaneMode(false)
            assertThat(underTest.icon).isNull()

            // Turn on airplane mode
            airplaneModeRepository.setIsAirplaneMode(true)

            assertThat(underTest.icon).isEqualTo(expectedAirplaneIcon)

            // Turn off airplane mode
            airplaneModeRepository.setIsAirplaneMode(false)
            assertThat(underTest.icon).isNull()
        }

    @Test
    fun icon_updatesWhenForceHiddenChanges() =
        kosmos.runTest {
            airplaneModeRepository.setIsAirplaneMode(true)

            // Start not hidden
            fakeConnectivityRepository.setForceHiddenIcons(setOf())

            assertThat(underTest.icon).isEqualTo(expectedAirplaneIcon)

            // Force hide
            fakeConnectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
            assertThat(underTest.icon).isNull()

            // Un-hide
            fakeConnectivityRepository.setForceHiddenIcons(setOf())
            assertThat(underTest.icon).isEqualTo(expectedAirplaneIcon)
        }
}
+4 −0
Original line number Diff line number Diff line
@@ -42,6 +42,10 @@ interface AirplaneModeViewModel {
    val isAirplaneModeIconVisible: StateFlow<Boolean>
}

@Deprecated(
    message = "This view model will not be used once SystemStatusIconsInCompose is launched",
    replaceWith = ReplaceWith("AirplaneModeIconViewModel"),
)
@SysUISingleton
class AirplaneModeViewModelImpl
@Inject
Loading