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

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

Merge "Migrate DataSaverTile" into main

parents 93ecf594 f5bbf836
Loading
Loading
Loading
Loading
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.saver.domain

import android.content.SharedPreferences
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.leaks.FakeDataSaverController
import kotlin.coroutines.EmptyCoroutineContext
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify

/** Test [DataSaverDialogDelegate]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class DataSaverDialogDelegateTest : SysuiTestCase() {

    private val dataSaverController = FakeDataSaverController(LeakCheck())

    private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
    private lateinit var sysuiDialog: SystemUIDialog
    private lateinit var dataSaverDialogDelegate: DataSaverDialogDelegate

    @Before
    fun setup() {
        sysuiDialog = mock<SystemUIDialog>()
        sysuiDialogFactory = mock<SystemUIDialog.Factory>()

        dataSaverDialogDelegate =
            DataSaverDialogDelegate(
                sysuiDialogFactory,
                context,
                EmptyCoroutineContext,
                dataSaverController,
                mock<SharedPreferences>()
            )

        whenever(sysuiDialogFactory.create(eq(dataSaverDialogDelegate), eq(context)))
            .thenReturn(sysuiDialog)
    }
    @Test
    fun delegateSetsDialogTitleCorrectly() {
        val expectedResId = R.string.data_saver_enable_title

        dataSaverDialogDelegate.onCreate(sysuiDialog, null)

        verify(sysuiDialog).setTitle(eq(expectedResId))
    }

    @Test
    fun delegateSetsDialogMessageCorrectly() {
        val expectedResId = R.string.data_saver_description

        dataSaverDialogDelegate.onCreate(sysuiDialog, null)

        verify(sysuiDialog).setMessage(expectedResId)
    }

    @Test
    fun delegateSetsDialogPositiveButtonCorrectly() {
        val expectedResId = R.string.data_saver_enable_button

        dataSaverDialogDelegate.onCreate(sysuiDialog, null)

        verify(sysuiDialog).setPositiveButton(eq(expectedResId), any())
    }

    @Test
    fun delegateSetsDialogCancelButtonCorrectly() {
        val expectedResId = R.string.cancel

        dataSaverDialogDelegate.onCreate(sysuiDialog, null)

        verify(sysuiDialog).setNeutralButton(eq(expectedResId), eq(null))
    }
}
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.saver.domain

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.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.qs.tiles.impl.saver.qsDataSaverTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class DataSaverTileMapperTest : SysuiTestCase() {
    private val kosmos = Kosmos()
    private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig

    // Using lazy (versus =) to make sure we override the right context -- see b/311612168
    private val mapper by lazy { DataSaverTileMapper(context.orCreateTestableResources.resources) }

    @Test
    fun activeStateMatchesEnabledModel() {
        val inputModel = DataSaverTileModel(true)

        val outputState = mapper.map(dataSaverTileConfig, inputModel)

        val expectedState =
            createDataSaverTileState(
                QSTileState.ActivationState.ACTIVE,
                R.drawable.qs_data_saver_icon_on
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    @Test
    fun inactiveStateMatchesDisabledModel() {
        val inputModel = DataSaverTileModel(false)

        val outputState = mapper.map(dataSaverTileConfig, inputModel)

        val expectedState =
            createDataSaverTileState(
                QSTileState.ActivationState.INACTIVE,
                R.drawable.qs_data_saver_icon_off
            )
        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
    }

    private fun createDataSaverTileState(
        activationState: QSTileState.ActivationState,
        iconRes: Int
    ): QSTileState {
        val label = context.getString(R.string.data_saver)
        val secondaryLabel =
            if (activationState == QSTileState.ActivationState.ACTIVE)
                context.resources.getStringArray(R.array.tile_states_saver)[2]
            else if (activationState == QSTileState.ActivationState.INACTIVE)
                context.resources.getStringArray(R.array.tile_states_saver)[1]
            else context.resources.getStringArray(R.array.tile_states_saver)[0]

        return QSTileState(
            { Icon.Resource(iconRes, null) },
            label,
            activationState,
            secondaryLabel,
            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
            label,
            null,
            QSTileState.SideViewIcon.None,
            QSTileState.EnabledState.ENABLED,
            Switch::class.qualifiedName
        )
    }
}
+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.saver.domain.interactor

import android.os.UserHandle
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.coroutines.collectValues
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.utils.leaks.FakeDataSaverController
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DataSaverTileDataInteractorTest : SysuiTestCase() {
    private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck())
    private val underTest: DataSaverTileDataInteractor = DataSaverTileDataInteractor(controller)

    @Test
    fun isAvailableRegardlessOfController() = runTest {
        controller.setDataSaverEnabled(false)

        runCurrent()
        val availability by collectLastValue(underTest.availability(TEST_USER))

        Truth.assertThat(availability).isTrue()
    }

    @Test
    fun dataMatchesController() = runTest {
        controller.setDataSaverEnabled(false)
        val flowValues: List<DataSaverTileModel> by
            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))

        runCurrent()
        controller.setDataSaverEnabled(true)
        runCurrent()
        controller.setDataSaverEnabled(false)
        runCurrent()

        Truth.assertThat(flowValues.size).isEqualTo(3)
        Truth.assertThat(flowValues.map { it.isEnabled })
            .containsExactly(false, true, false)
            .inOrder()
    }

    private companion object {
        val TEST_USER = UserHandle.of(1)!!
    }
}
+183 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.saver.domain.interactor

import android.content.Context
import android.content.SharedPreferences
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.animation.DialogLaunchAnimator
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.intentInputs
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.settings.UserFileManager
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.leaks.FakeDataSaverController
import com.google.common.truth.Truth.assertThat
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify

@SmallTest
@RunWith(AndroidJUnit4::class)
class DataSaverTileUserActionInteractorTest : SysuiTestCase() {
    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
    private val dataSaverController = FakeDataSaverController(LeakCheck())

    private lateinit var userFileManager: UserFileManager
    private lateinit var sharedPreferences: SharedPreferences
    private lateinit var dialogFactory: SystemUIDialog.Factory
    private lateinit var underTest: DataSaverTileUserActionInteractor

    @Before
    fun setup() {
        userFileManager = mock<UserFileManager>()
        sharedPreferences = mock<SharedPreferences>()
        dialogFactory = mock<SystemUIDialog.Factory>()
        whenever(
                userFileManager.getSharedPreferences(
                    eq(DataSaverTileUserActionInteractor.PREFS),
                    eq(Context.MODE_PRIVATE),
                    eq(context.userId)
                )
            )
            .thenReturn(sharedPreferences)

        underTest =
            DataSaverTileUserActionInteractor(
                context,
                EmptyCoroutineContext,
                EmptyCoroutineContext,
                dataSaverController,
                qsTileIntentUserActionHandler,
                mock<DialogLaunchAnimator>(),
                dialogFactory,
                userFileManager,
            )
    }

    /** Since the dialog was shown before, we expect the click to enable the controller. */
    @Test
    fun handleClickToEnableDialogShownBefore() = runTest {
        whenever(
                sharedPreferences.getBoolean(
                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
                    any()
                )
            )
            .thenReturn(true)
        val stateBeforeClick = false

        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick)))

        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!stateBeforeClick)
    }

    /**
     * The first time the tile is clicked to turn on we expect (1) the enabled state to not change
     * and (2) the dialog to be shown instead.
     */
    @Test
    fun handleClickToEnableDialogNotShownBefore() = runTest {
        whenever(
                sharedPreferences.getBoolean(
                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
                    any()
                )
            )
            .thenReturn(false)
        val mockDialog = mock<SystemUIDialog>()
        whenever(dialogFactory.create(any(), any())).thenReturn(mockDialog)
        val stateBeforeClick = false

        val input = QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick))
        underTest.handleInput(input)

        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(stateBeforeClick)
        verify(mockDialog).show()
    }

    /** Disabling should flip the state, even if the dialog was not shown before. */
    @Test
    fun handleClickToDisableDialogNotShownBefore() = runTest {
        whenever(
                sharedPreferences.getBoolean(
                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
                    any()
                )
            )
            .thenReturn(false)
        val enabledBeforeClick = true

        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))

        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
    }

    @Test
    fun handleClickToDisableDialogShownBefore() = runTest {
        whenever(
                sharedPreferences.getBoolean(
                    eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN),
                    any()
                )
            )
            .thenReturn(true)
        val enabledBeforeClick = true

        underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick)))

        assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick)
    }

    @Test
    fun handleLongClickWhenEnabled() = runTest {
        val enabledState = true

        underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))

        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
        val actualIntentAction = intentInput.intent.action
        val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
    }

    @Test
    fun handleLongClickWhenDisabled() = runTest {
        val enabledState = false

        underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState)))

        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
        val actualIntentAction = intentInput.intent.action
        val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS
        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
    }
}
+60 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.saver.domain

import android.content.Context
import android.content.DialogInterface
import android.content.SharedPreferences
import android.os.Bundle
import com.android.internal.R
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DataSaverController
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

class DataSaverDialogDelegate(
    private val sysuiDialogFactory: SystemUIDialog.Factory,
    private val context: Context,
    private val backgroundContext: CoroutineContext,
    private val dataSaverController: DataSaverController,
    private val sharedPreferences: SharedPreferences,
) : SystemUIDialog.Delegate {
    override fun createDialog(): SystemUIDialog {
        return sysuiDialogFactory.create(this, context)
    }

    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
        with(dialog) {
            setTitle(R.string.data_saver_enable_title)
            setMessage(R.string.data_saver_description)
            setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ ->
                CoroutineScope(backgroundContext).launch {
                    dataSaverController.setDataSaverEnabled(true)
                }

                sharedPreferences
                    .edit()
                    .putBoolean(DataSaverTileUserActionInteractor.DIALOG_SHOWN, true)
                    .apply()
            }
            setNeutralButton(R.string.cancel, null)
            setShowForAllUsers(true)
        }
    }
}
Loading