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

Commit f5bbf836 authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Migrate DataSaverTile

Fixes: 301056034
Flag: LEGACY QS_PIPELINE_NEW_TILES DISABLED
Test: atest SystemUiRoboTests
Test: atest DataSaverTileDataInteractor DataSaverTileUserActionInteractor DataSaverTileMapper
Change-Id: Ic8b51335e8508aecb04455b71c3dfc49ff103a9e
parent 874cb8e5
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