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

Commit 24dd3b5b authored by Behnam Heydarshahi's avatar Behnam Heydarshahi Committed by Android (Google) Code Review
Browse files

Merge "Migrate BatterySaverTile" into main

parents 5e3340b2 192961db
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