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

Commit 192961db authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Migrate BatterySaverTile

Flag: aconfig com.android.systemui.qs_new_tiles DEVELOPMENT
Fixes: 301055775
Test: atest SystemUiRoboTests
Test: atest BatterySaverTileDataInteractorTest BatterySaverTileUserActionInteractorTest BatterySaverTileMapperTest BatterySaverTileDataInteractorGoogleTest

Change-Id: Ifba944f629b213449f523d1d197329ac6eac721b
parent 20faed1b
Loading
Loading
Loading
Loading
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.qs.tiles.impl.battery.doman.interactor

import android.os.UserHandle
import android.platform.test.annotations.EnabledOnRavenwood
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
import com.android.systemui.utils.leaks.FakeBatteryController
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toCollection
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class BatterySaverTileDataInteractorTest : SysuiTestCase() {
    private val kosmos = Kosmos()
    private val testScope = kosmos.testScope
    private val batteryController = FakeBatteryController(LeakCheck())
    private val testUser = UserHandle.of(1)
    private val underTest =
        BatterySaverTileDataInteractor(testScope.testScheduler, batteryController)

    @Test
    fun availability_isTrue() =
        testScope.runTest {
            val availability = underTest.availability(testUser).toCollection(mutableListOf())

            Truth.assertThat(availability).hasSize(1)
            Truth.assertThat(availability.last()).isTrue()
        }

    @Test
    fun tileData_matchesBatteryControllerPowerSaving() =
        testScope.runTest {
            val data by
                collectLastValue(
                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
                )

            runCurrent()
            Truth.assertThat(data!!.isPowerSaving).isFalse()

            batteryController.setPowerSaveMode(true)
            runCurrent()
            Truth.assertThat(data!!.isPowerSaving).isTrue()

            batteryController.setPowerSaveMode(false)
            runCurrent()
            Truth.assertThat(data!!.isPowerSaving).isFalse()
        }

    @Test
    fun tileData_matchesBatteryControllerIsPluggedIn() =
        testScope.runTest {
            val data by
                collectLastValue(
                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
                )

            runCurrent()
            Truth.assertThat(data!!.isPluggedIn).isFalse()

            batteryController.isPluggedIn = true
            runCurrent()
            Truth.assertThat(data!!.isPluggedIn).isTrue()

            batteryController.isPluggedIn = false
            runCurrent()
            Truth.assertThat(data!!.isPluggedIn).isFalse()
        }
}
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.qs.tiles.impl.battery.doman.interactor

import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
import com.android.systemui.utils.leaks.FakeBatteryController
import com.google.common.truth.Truth
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class BatterySaverTileUserActionInteractorTest : SysuiTestCase() {
    private val inputHandler = FakeQSTileIntentUserInputHandler()
    private val controller = FakeBatteryController(LeakCheck())
    private val underTest = BatterySaverTileUserActionInteractor(inputHandler, controller)

    @Test
    fun handleClickWhenNotPluggedIn_flipsPowerSaverMode() = runTest {
        val originalPowerSaveMode = controller.isPowerSave
        controller.isPluggedIn = false

        underTest.handleInput(
            QSTileInputTestKtx.click(BatterySaverTileModel.Standard(false, originalPowerSaveMode))
        )

        Truth.assertThat(controller.isPowerSave).isNotEqualTo(originalPowerSaveMode)
    }

    @Test
    fun handleClickWhenPluggedIn_doesNotTurnOnPowerSaverMode() = runTest {
        controller.setPowerSaveMode(false)
        val originalPowerSaveMode = controller.isPowerSave
        controller.isPluggedIn = true

        underTest.handleInput(
            QSTileInputTestKtx.click(
                BatterySaverTileModel.Standard(controller.isPluggedIn, originalPowerSaveMode)
            )
        )

        Truth.assertThat(controller.isPowerSave).isEqualTo(originalPowerSaveMode)
    }

    @Test
    fun handleLongClick() = runTest {
        underTest.handleInput(
            QSTileInputTestKtx.longClick(BatterySaverTileModel.Standard(false, false))
        )

        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
            Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_BATTERY_SAVER_SETTINGS)
        }
    }
}
+270 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.qs.tiles.impl.battery.ui

import android.graphics.drawable.TestStubDrawable
import android.widget.Switch
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class BatterySaverTileMapperTest : SysuiTestCase() {
    private val kosmos = Kosmos()
    private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig
    private lateinit var mapper: BatterySaverTileMapper

    @Before
    fun setup() {
        mapper =
            BatterySaverTileMapper(
                context.orCreateTestableResources
                    .apply {
                        addOverride(R.drawable.qs_battery_saver_icon_off, TestStubDrawable())
                        addOverride(R.drawable.qs_battery_saver_icon_on, TestStubDrawable())
                    }
                    .resources,
                context.theme,
            )
    }

    @Test
    fun map_standard_notPluggedInNotPowerSaving() {
        val inputModel = BatterySaverTileModel.Standard(false, false)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.INACTIVE,
                "",
                R.drawable.qs_battery_saver_icon_off,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_standard_notPluggedInPowerSaving() {
        val inputModel = BatterySaverTileModel.Standard(false, true)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.ACTIVE,
                "",
                R.drawable.qs_battery_saver_icon_on,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_standard_pluggedInPowerSaving() {
        val inputModel = BatterySaverTileModel.Standard(true, true)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.UNAVAILABLE,
                "",
                R.drawable.qs_battery_saver_icon_on,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_standard_pluggedInNotPowerSaving() {
        val inputModel = BatterySaverTileModel.Standard(true, false)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.UNAVAILABLE,
                "",
                R.drawable.qs_battery_saver_icon_off,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverDisabledNotPluggedInNotPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(false, false, false)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.INACTIVE,
                "",
                R.drawable.qs_battery_saver_icon_off,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverDisabledNotPluggedInPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(false, true, false)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.ACTIVE,
                context.getString(R.string.standard_battery_saver_text),
                R.drawable.qs_battery_saver_icon_on,
                context.getString(R.string.standard_battery_saver_text),
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverDisabledPluggedInPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(true, true, false)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.UNAVAILABLE,
                "",
                R.drawable.qs_battery_saver_icon_on,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverDisabledPluggedInNotPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(true, false, false)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.UNAVAILABLE,
                "",
                R.drawable.qs_battery_saver_icon_off,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverEnabledNotPluggedInNotPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(false, false, true)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.INACTIVE,
                "",
                R.drawable.qs_battery_saver_icon_off,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverEnabledNotPluggedInPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(false, true, true)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.ACTIVE,
                context.getString(R.string.extreme_battery_saver_text),
                R.drawable.qs_battery_saver_icon_on,
                context.getString(R.string.extreme_battery_saver_text),
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverEnabledPluggedInPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(true, true, true)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.UNAVAILABLE,
                "",
                R.drawable.qs_battery_saver_icon_on,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun map_extremeSaverEnabledPluggedInNotPowerSaving() {
        val inputModel = BatterySaverTileModel.Extreme(true, false, true)

        val outputState = mapper.map(batterySaverTileConfig, inputModel)

        val expectedState =
            createBatterySaverTileState(
                QSTileState.ActivationState.UNAVAILABLE,
                "",
                R.drawable.qs_battery_saver_icon_off,
                null,
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    private fun createBatterySaverTileState(
        activationState: QSTileState.ActivationState,
        secondaryLabel: String,
        iconRes: Int,
        stateDescription: CharSequence?,
    ): QSTileState {
        val label = context.getString(R.string.battery_detail_switch_title)
        return QSTileState(
            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
            label,
            activationState,
            secondaryLabel,
            if (activationState == QSTileState.ActivationState.UNAVAILABLE)
                setOf(QSTileState.UserAction.LONG_CLICK)
            else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
            label,
            stateDescription,
            QSTileState.SideViewIcon.None,
            QSTileState.EnabledState.ENABLED,
            Switch::class.qualifiedName
        )
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -75,6 +75,11 @@
    <!-- Battery saver notification dismiss action: Do not turn on battery saver. [CHAR LIMIT=NONE]-->
    <string name="battery_saver_dismiss_action">No thanks</string>

    <!-- Secondary label for Battery Saver tile when Battery Saver is enabled. [CHAR LIMIT=20] -->
    <string name="standard_battery_saver_text">Standard</string>
    <!-- Secondary label for Battery Saver tile when Extreme Battery Saver is enabled. [CHAR LIMIT=20] -->
    <string name="extreme_battery_saver_text">Extreme</string>

    <!-- Name of the button that links to the Settings app. [CHAR LIMIT=NONE] -->

    <!-- Name of the button that links to the Wifi settings screen. [CHAR LIMIT=NONE] -->
+47 −0
Original line number Diff line number Diff line
package com.android.systemui.battery

import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.BatterySaverTile
import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
import com.android.systemui.qs.tiles.impl.battery.ui.BatterySaverTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey

@@ -15,4 +27,39 @@ interface BatterySaverModule {
    @IntoMap
    @StringKey(BatterySaverTile.TILE_SPEC)
    fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>

    companion object {
        private const val BATTERY_SAVER_TILE_SPEC = "battery"

        @Provides
        @IntoMap
        @StringKey(BATTERY_SAVER_TILE_SPEC)
        fun provideBatterySaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
            QSTileConfig(
                tileSpec = TileSpec.create(BATTERY_SAVER_TILE_SPEC),
                uiConfig =
                    QSTileUIConfig.Resource(
                        iconRes = R.drawable.qs_battery_saver_icon_off,
                        labelRes = R.string.battery_detail_switch_title,
                    ),
                instanceId = uiEventLogger.getNewInstanceId(),
            )

        /** Inject BatterySaverTile into tileViewModelMap in QSModule */
        @Provides
        @IntoMap
        @StringKey(BATTERY_SAVER_TILE_SPEC)
        fun provideBatterySaverTileViewModel(
            factory: QSTileViewModelFactory.Static<BatterySaverTileModel>,
            mapper: BatterySaverTileMapper,
            stateInteractor: BatterySaverTileDataInteractor,
            userActionInteractor: BatterySaverTileUserActionInteractor
        ): QSTileViewModel =
            factory.create(
                TileSpec.create(BATTERY_SAVER_TILE_SPEC),
                userActionInteractor,
                stateInteractor,
                mapper,
            )
    }
}
Loading