Skip to content
Snippets Groups Projects
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
No related branches found
No related tags found
No related merge requests found
Showing
with 830 additions and 1 deletion
/*
* 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))
}
}
/*
* 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
)
}
}
/*
* 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)!!
}
}
/*
* 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)
}
}
/*
* 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)
}
}
}
/*
* 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.res.Resources
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import javax.inject.Inject
/** Maps [DataSaverTileModel] to [QSTileState]. */
class DataSaverTileMapper @Inject constructor(@Main private val resources: Resources) :
QSTileDataToStateMapper<DataSaverTileModel> {
override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
QSTileState.build(resources, config.uiConfig) {
with(data) {
if (isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
icon = { Icon.Resource(R.drawable.qs_data_saver_icon_on, null) }
secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2]
} else {
activationState = QSTileState.ActivationState.INACTIVE
icon = { Icon.Resource(R.drawable.qs_data_saver_icon_off, null) }
secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
}
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
}
}
}
/*
* 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 com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.statusbar.policy.DataSaverController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
/** Observes data saver state changes providing the [DataSaverTileModel]. */
class DataSaverTileDataInteractor
@Inject
constructor(
private val dataSaverController: DataSaverController,
) : QSTileDataInteractor<DataSaverTileModel> {
override fun tileData(
user: UserHandle,
triggers: Flow<DataUpdateTrigger>
): Flow<DataSaverTileModel> =
ConflatedCallbackFlow.conflatedCallbackFlow {
val initialValue = dataSaverController.isDataSaverEnabled
trySend(DataSaverTileModel(initialValue))
val callback = DataSaverController.Listener { trySend(DataSaverTileModel(it)) }
dataSaverController.addCallback(callback)
awaitClose { dataSaverController.removeCallback(callback) }
}
override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
}
/*
* 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.Intent
import android.provider.Settings
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverDialogDelegate
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.settings.UserFileManager
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DataSaverController
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext
/** Handles data saver tile clicks. */
class DataSaverTileUserActionInteractor
@Inject
constructor(
@Application private val context: Context,
@Main private val coroutineContext: CoroutineContext,
@Background private val backgroundContext: CoroutineContext,
private val dataSaverController: DataSaverController,
private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val systemUIDialogFactory: SystemUIDialog.Factory,
userFileManager: UserFileManager,
) : QSTileUserActionInteractor<DataSaverTileModel> {
companion object {
private const val INTERACTION_JANK_TAG = "start_data_saver"
const val PREFS = "data_saver"
const val DIALOG_SHOWN = "data_saver_dialog_shown"
}
val sharedPreferences =
userFileManager.getSharedPreferences(PREFS, Context.MODE_PRIVATE, context.userId)
override suspend fun handleInput(input: QSTileInput<DataSaverTileModel>): Unit =
with(input) {
when (action) {
is QSTileUserAction.Click -> {
val wasEnabled: Boolean = data.isEnabled
if (wasEnabled || sharedPreferences.getBoolean(DIALOG_SHOWN, false)) {
withContext(backgroundContext) {
dataSaverController.setDataSaverEnabled(!wasEnabled)
}
return@with
}
// Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator
// must be created and shown on the main thread, so we post it to the UI
// handler
withContext(coroutineContext) {
val dialogContext = action.view?.context ?: context
val dialogDelegate =
DataSaverDialogDelegate(
systemUIDialogFactory,
dialogContext,
backgroundContext,
dataSaverController,
sharedPreferences
)
val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext)
if (action.view != null) {
dialogLaunchAnimator.showFromView(
dialog,
action.view!!,
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG
)
)
} else {
dialog.show()
}
}
}
is QSTileUserAction.LongClick -> {
qsTileIntentUserActionHandler.handle(
action.view,
Intent(Settings.ACTION_DATA_SAVER_SETTINGS)
)
}
}
}
}
/*
* 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.model
/**
* data saver tile model.
*
* @param isEnabled is true when the data saver is enabled;
*/
@JvmInline value class DataSaverTileModel(val isEnabled: Boolean)
......@@ -35,6 +35,10 @@ import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
......@@ -85,6 +89,7 @@ interface ConnectivityModule {
companion object {
const val AIRPLANE_MODE_TILE_SPEC = "airplane"
const val DATA_SAVER_TILE_SPEC = "saver"
/** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
@Provides
......@@ -132,5 +137,36 @@ interface ConnectivityModule {
stateInteractor,
mapper,
)
@Provides
@IntoMap
@StringKey(DATA_SAVER_TILE_SPEC)
fun provideDataSaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
QSTileConfig(
tileSpec = TileSpec.create(DATA_SAVER_TILE_SPEC),
uiConfig =
QSTileUIConfig.Resource(
iconRes = R.drawable.qs_data_saver_icon_off,
labelRes = R.string.data_saver,
),
instanceId = uiEventLogger.getNewInstanceId(),
)
/** Inject DataSaverTile into tileViewModelMap in QSModule */
@Provides
@IntoMap
@StringKey(DATA_SAVER_TILE_SPEC)
fun provideDataSaverTileViewModel(
factory: QSTileViewModelFactory.Static<DataSaverTileModel>,
mapper: DataSaverTileMapper,
stateInteractor: DataSaverTileDataInteractor,
userActionInteractor: DataSaverTileUserActionInteractor
): QSTileViewModel =
factory.create(
TileSpec.create(DATA_SAVER_TILE_SPEC),
userActionInteractor,
stateInteractor,
mapper,
)
}
}
/*
* 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
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.qsEventLogger
import com.android.systemui.statusbar.connectivity.ConnectivityModule
val Kosmos.qsDataSaverTileConfig by
Kosmos.Fixture { ConnectivityModule.provideDataSaverTileConfig(qsEventLogger) }
......@@ -19,19 +19,38 @@ import android.testing.LeakCheck;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import java.util.ArrayList;
import java.util.List;
public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
private boolean mIsEnabled = false;
private List<Listener> mListeners = new ArrayList<>();
public FakeDataSaverController(LeakCheck test) {
super(test, "datasaver");
}
@Override
public boolean isDataSaverEnabled() {
return false;
return mIsEnabled;
}
@Override
public void setDataSaverEnabled(boolean enabled) {
mIsEnabled = enabled;
for (Listener listener: mListeners) {
listener.onDataSaverChanged(enabled);
}
}
@Override
public void addCallback(Listener listener) {
mListeners.add(listener);
}
@Override
public void removeCallback(Listener listener) {
mListeners.remove(listener);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment