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

Commit 913d4058 authored by Evan Laird's avatar Evan Laird Committed by Android (Google) Code Review
Browse files

Merge changes I98ce05fb,I894661c1 into main

* changes:
  [sb] NewStatusBarIcons helper class
  [battery] Compose-based battery icon
parents a54b30c1 c23a4e96
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.activityStarter
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.statusbar.policy.fake
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.settings.fakeSettings
@@ -47,7 +48,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever

@SmallTest
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
@@ -69,7 +69,7 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
    fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() =
        with(kosmos) {
            runTest {
                whenever(batteryController.isPluggedIn()).thenReturn(true)
                batteryController.fake._isPluggedIn = true
                runCurrent()

                assertThat(underTest.shouldShowDreamButtonOnHub).isTrue()
@@ -80,8 +80,7 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
    fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() =
        with(kosmos) {
            runTest {
                whenever(batteryController.isPluggedIn()).thenReturn(false)
                runCurrent()
                batteryController.fake._isPluggedIn = false

                assertThat(underTest.shouldShowDreamButtonOnHub).isFalse()
            }
+5 −22
Original line number Diff line number Diff line
@@ -39,11 +39,9 @@ import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.statusbar.policy.fake
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -52,7 +50,6 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@@ -219,23 +216,12 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCa
            val latest by collectLastValue(underTest.isBatteryCharging)
            runCurrent()

            val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
            verify(batteryController).addCallback(capture(captor))
            val callback = captor.value

            callback.onBatteryLevelChanged(
                /* level= */ 2,
                /* pluggedIn= */ false,
                /* charging= */ true,
            )
            batteryController.fake._level = 2
            batteryController.fake._isPluggedIn = true

            assertThat(latest).isTrue()

            callback.onBatteryLevelChanged(
                /* level= */ 2,
                /* pluggedIn= */ true,
                /* charging= */ false,
            )
            batteryController.fake._isPluggedIn = false

            assertThat(latest).isFalse()
        }
@@ -246,12 +232,9 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCa
            val job = underTest.isBatteryCharging.launchIn(this)
            runCurrent()

            val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
            verify(batteryController).addCallback(capture(captor))

            job.cancel()
            runCurrent()

            verify(batteryController).removeCallback(captor.value)
            assertThat(batteryController.fake.listeners).isEmpty()
        }
}
+60 −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.core

import com.android.settingslib.flags.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils

/** Helper for reading and using the status bar simple fragment flag state */
object NewStatusBarIcons {
    /** Aconfig flag for new status bar icons */
    const val FLAG_NAME = Flags.FLAG_NEW_STATUS_BAR_ICONS

    /** A token used for dependency declaration */
    val token: FlagToken
        get() = FlagToken(FLAG_NAME, isEnabled)

    /** Is the refactor enabled */
    @JvmStatic
    inline val isEnabled
        get() = Flags.newStatusBarIcons()

    /**
     * Called to ensure code is only run when the flag is enabled. This protects users from the
     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
     * build to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun isUnexpectedlyInLegacyMode() =
        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)

    /**
     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
     * the flag is not enabled to ensure that the refactor author catches issues in testing.
     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
     */
    @JvmStatic
    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)

    /**
     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
     * the flag is enabled to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
}
+49 −1
Original line number Diff line number Diff line
@@ -15,15 +15,63 @@
 */
package com.android.systemui.statusbar.phone.domain.interactor

import android.graphics.Rect
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
import com.android.systemui.statusbar.phone.domain.model.DarkState
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map

/** States pertaining to calculating colors for icons in dark mode. */
@SysUISingleton
class DarkIconInteractor @Inject constructor(private val repository: DarkIconRepository) {
    /** Dark-mode state for tinting icons. */
    fun darkState(displayId: Int): Flow<DarkState> =
        repository.darkState(displayId).map { DarkState(it.areas, it.tint) }
        repository.darkState(displayId).map { DarkState(it.areas, it.tint, it.darkIntensity) }

    /**
     * Given a display id: returns a flow of [IsAreaDark], a function that can tell you if a given
     * [Rect] should be tinted dark or not. This flow ignores [DarkChange.tint] and
     * [DarkChange.darkIntensity]
     */
    fun isAreaDark(displayId: Int): Flow<IsAreaDark> {
        return repository.darkState(displayId).toIsAreaDark()
    }

    companion object {
        /**
         * Convenience function to convert between the repository's [darkState] into [IsAreaDark]
         * type flows.
         */
        @JvmStatic
        fun Flow<DarkChange>.toIsAreaDark(): Flow<IsAreaDark> =
            map { darkChange ->
                    DarkStateWithoutIntensity(darkChange.areas, darkChange.darkIntensity < 0.5f)
                }
                .distinctUntilChanged()
                .map { darkState ->
                    IsAreaDark { viewBounds: Rect ->
                        if (DarkIconDispatcher.isInAreas(darkState.areas, viewBounds)) {
                            darkState.isDark
                        } else {
                            false
                        }
                    }
                }
                .conflate()
                .distinctUntilChanged()
    }
}

/** So we can map between [DarkState] and a single boolean, but based on intensity */
private data class DarkStateWithoutIntensity(val areas: Collection<Rect>, val isDark: Boolean)

/** Given a region on screen, determine if the foreground should be dark or light */
fun interface IsAreaDark {
    fun isDark(viewBounds: Rect): Boolean
}
+2 −0
Original line number Diff line number Diff line
@@ -24,4 +24,6 @@ data class DarkState(
    val areas: Collection<Rect>,
    /** Tint color to apply to UI elements that fall within [areas]. */
    val tint: Int,
    /** _How_ dark the area is. Less than 0.5 is dark, otherwise light */
    val darkIntensity: Float,
)
Loading