Add AutoAddables

AutoAddable encapsulates a particular tile that can be auto-added to
Quick Settings and the triggers needed for it.

Test: atest com.android.systemui.qs.pipeline.interactor.autoaddable
Bug: 278577795
Change-Id: I99d3856c2b71a8604c586e8b6df2306e726168e9
This commit is contained in:
Fabián Kozynski
2023-06-15 17:08:05 -04:00
parent c268174cb0
commit 2c770a9438
30 changed files with 2448 additions and 1 deletions

View File

@@ -0,0 +1,73 @@
/*
* 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.pipeline.dagger
import android.content.res.Resources
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.DataSaverAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.DeviceControlsAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.HotspotAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.NightDisplayAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.ReduceBrightColorsAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.WalletAutoAddable
import com.android.systemui.qs.pipeline.domain.autoaddable.WorkTileAutoAddable
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
import dagger.multibindings.IntoSet
@Module
interface BaseAutoAddableModule {
companion object {
@Provides
@ElementsIntoSet
fun providesAutoAddableSetting(
@Main resources: Resources,
autoAddableSettingFactory: AutoAddableSetting.Factory
): Set<AutoAddable> {
return AutoAddableSettingList.parseSettingsResource(
resources,
autoAddableSettingFactory
)
.toSet()
}
}
@Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
@Binds @IntoSet fun bindDataSaverAutoAddable(impl: DataSaverAutoAddable): AutoAddable
@Binds @IntoSet fun bindDeviceControlsAutoAddable(impl: DeviceControlsAutoAddable): AutoAddable
@Binds @IntoSet fun bindHotspotAutoAddable(impl: HotspotAutoAddable): AutoAddable
@Binds @IntoSet fun bindNightDisplayAutoAddable(impl: NightDisplayAutoAddable): AutoAddable
@Binds
@IntoSet
fun bindReduceBrightColorsAutoAddable(impl: ReduceBrightColorsAutoAddable): AutoAddable
@Binds @IntoSet fun bindWalletAutoAddable(impl: WalletAutoAddable): AutoAddable
@Binds @IntoSet fun bindWorkModeAutoAddable(impl: WorkTileAutoAddable): AutoAddable
}

View File

@@ -0,0 +1,82 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.Objects
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/**
* It tracks a specific `Secure` int [setting] and when its value changes to non-zero, it will emit
* a [AutoAddSignal.Add] for [spec].
*/
class AutoAddableSetting
@AssistedInject
constructor(
private val secureSettings: SecureSettings,
@Background private val bgDispatcher: CoroutineDispatcher,
@Assisted private val setting: String,
@Assisted private val spec: TileSpec,
) : AutoAddable {
override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return secureSettings
.observerFlow(userId, setting)
.onStart { emit(Unit) }
.map { secureSettings.getIntForUser(setting, 0, userId) != 0 }
.distinctUntilChanged()
.filter { it }
.map { AutoAddSignal.Add(spec) }
.flowOn(bgDispatcher)
}
override val autoAddTracking = AutoAddTracking.IfNotAdded(spec)
override val description = "AutoAddableSetting: $setting:$spec ($autoAddTracking)"
override fun equals(other: Any?): Boolean {
return other is AutoAddableSetting && spec == other.spec && setting == other.setting
}
override fun hashCode(): Int {
return Objects.hash(spec, setting)
}
override fun toString(): String {
return description
}
@AssistedFactory
interface Factory {
fun create(setting: String, spec: TileSpec): AutoAddableSetting
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.pipeline.domain.autoaddable
import android.content.res.Resources
import android.util.Log
import com.android.systemui.R
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
object AutoAddableSettingList {
/** Parses [R.array.config_quickSettingsAutoAdd] into a collection of [AutoAddableSetting]. */
fun parseSettingsResource(
resources: Resources,
autoAddableSettingFactory: AutoAddableSetting.Factory,
): Iterable<AutoAddable> {
val autoAddList = resources.getStringArray(R.array.config_quickSettingsAutoAdd)
return autoAddList.mapNotNull {
val elements = it.split(SETTING_SEPARATOR, limit = 2)
if (elements.size == 2) {
val setting = elements[0]
val spec = elements[1]
val tileSpec = TileSpec.create(spec)
if (tileSpec == TileSpec.Invalid) {
Log.w(TAG, "Malformed item in array: $it")
null
} else {
autoAddableSettingFactory.create(setting, TileSpec.create(spec))
}
} else {
Log.w(TAG, "Malformed item in array: $it")
null
}
}
}
private const val SETTING_SEPARATOR = ":"
private const val TAG = "AutoAddableSettingList"
}

View File

@@ -0,0 +1,60 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.statusbar.policy.CallbackController
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
/** Generic [AutoAddable] for tiles that are added based on a signal from a [CallbackController]. */
abstract class CallbackControllerAutoAddable<
Callback : Any, Controller : CallbackController<Callback>>(
private val controller: Controller,
) : AutoAddable {
/** [TileSpec] for the tile to add. */
protected abstract val spec: TileSpec
/**
* Callback to be used to determine when to add the tile. When the callback determines that the
* feature has been enabled, it should call [sendAdd].
*/
protected abstract fun ProducerScope<AutoAddSignal>.getCallback(): Callback
/** Sends an [AutoAddSignal.Add] for [spec]. */
protected fun ProducerScope<AutoAddSignal>.sendAdd() {
trySend(AutoAddSignal.Add(spec))
}
final override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return conflatedCallbackFlow {
val callback = getCallback()
controller.addCallback(callback)
awaitClose { controller.removeCallback(callback) }
}
}
override val autoAddTracking: AutoAddTracking
get() = AutoAddTracking.IfNotAdded(spec)
}

View File

@@ -0,0 +1,56 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.CastTile
import com.android.systemui.statusbar.policy.CastController
import javax.inject.Inject
import kotlinx.coroutines.channels.ProducerScope
/**
* [AutoAddable] for [CastTile.TILE_SPEC].
*
* It will send a signal to add the tile when there's a casting device connected or connecting.
*/
@SysUISingleton
class CastAutoAddable
@Inject
constructor(
private val controller: CastController,
) : CallbackControllerAutoAddable<CastController.Callback, CastController>(controller) {
override val spec: TileSpec
get() = TileSpec.create(CastTile.TILE_SPEC)
override fun ProducerScope<AutoAddSignal>.getCallback(): CastController.Callback {
return CastController.Callback {
val isCasting =
controller.castDevices.any {
it.state == CastController.CastDevice.STATE_CONNECTED ||
it.state == CastController.CastDevice.STATE_CONNECTING
}
if (isCasting) {
sendAdd()
}
}
}
override val description = "CastAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,54 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.DataSaverTile
import com.android.systemui.statusbar.policy.DataSaverController
import javax.inject.Inject
import kotlinx.coroutines.channels.ProducerScope
/**
* [AutoAddable] for [DataSaverTile.TILE_SPEC].
*
* It will send a signal to add the tile when data saver is enabled.
*/
@SysUISingleton
class DataSaverAutoAddable
@Inject
constructor(
dataSaverController: DataSaverController,
) :
CallbackControllerAutoAddable<DataSaverController.Listener, DataSaverController>(
dataSaverController
) {
override val spec
get() = TileSpec.create(DataSaverTile.TILE_SPEC)
override fun ProducerScope<AutoAddSignal>.getCallback(): DataSaverController.Listener {
return DataSaverController.Listener { enabled ->
if (enabled) {
sendAdd()
}
}
}
override val description = "DataSaverAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,70 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.DeviceControlsTile
import com.android.systemui.statusbar.policy.DeviceControlsController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
/**
* [AutoAddable] for [DeviceControlsTile.TILE_SPEC].
*
* It will send a signal to add the tile when updating to a device that supports device controls. It
* will send a signal to remove the tile when the device does not support controls.
*/
@SysUISingleton
class DeviceControlsAutoAddable
@Inject
constructor(
private val deviceControlsController: DeviceControlsController,
) : AutoAddable {
private val spec = TileSpec.create(DeviceControlsTile.TILE_SPEC)
override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return conflatedCallbackFlow {
val callback =
object : DeviceControlsController.Callback {
override fun onControlsUpdate(position: Int?) {
position?.let { trySend(AutoAddSignal.Add(spec, position)) }
deviceControlsController.removeCallback()
}
override fun removeControlsAutoTracker() {
trySend(AutoAddSignal.Remove(spec))
}
}
deviceControlsController.setCallback(callback)
awaitClose { deviceControlsController.removeCallback() }
}
}
override val autoAddTracking: AutoAddTracking
get() = AutoAddTracking.Always
override val description = "DeviceControlsAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,54 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.HotspotTile
import com.android.systemui.statusbar.policy.HotspotController
import javax.inject.Inject
import kotlinx.coroutines.channels.ProducerScope
/**
* [AutoAddable] for [HotspotTile.TILE_SPEC].
*
* It will send a signal to add the tile when hotspot is enabled.
*/
@SysUISingleton
class HotspotAutoAddable
@Inject
constructor(
hotspotController: HotspotController,
) :
CallbackControllerAutoAddable<HotspotController.Callback, HotspotController>(
hotspotController
) {
override val spec
get() = TileSpec.create(HotspotTile.TILE_SPEC)
override fun ProducerScope<AutoAddSignal>.getCallback(): HotspotController.Callback {
return HotspotController.Callback { enabled, _ ->
if (enabled) {
sendAdd()
}
}
}
override val description = "HotspotAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,91 @@
/*
* 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.pipeline.domain.autoaddable
import android.content.Context
import android.hardware.display.ColorDisplayManager
import android.hardware.display.NightDisplayListener
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.NightDisplayListenerModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.NightDisplayTile
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
/**
* [AutoAddable] for [NightDisplayTile.TILE_SPEC].
*
* It will send a signal to add the tile when night display is enabled or when the auto mode changes
* to one that supports night display.
*/
@SysUISingleton
class NightDisplayAutoAddable
@Inject
constructor(
private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
context: Context,
) : AutoAddable {
private val enabled = ColorDisplayManager.isNightDisplayAvailable(context)
private val spec = TileSpec.create(NightDisplayTile.TILE_SPEC)
override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return conflatedCallbackFlow {
val nightDisplayListener = nightDisplayListenerBuilder.setUser(userId).build()
val callback =
object : NightDisplayListener.Callback {
override fun onActivated(activated: Boolean) {
if (activated) {
sendAdd()
}
}
override fun onAutoModeChanged(autoMode: Int) {
if (
autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME ||
autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT
) {
sendAdd()
}
}
private fun sendAdd() {
trySend(AutoAddSignal.Add(spec))
}
}
nightDisplayListener.setCallback(callback)
awaitClose { nightDisplayListener.setCallback(null) }
}
}
override val autoAddTracking =
if (enabled) {
AutoAddTracking.IfNotAdded(spec)
} else {
AutoAddTracking.Disabled
}
override val description = "NightDisplayAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,68 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.ReduceBrightColorsController
import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.ReduceBrightColorsTile
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.channels.ProducerScope
/**
* [AutoAddable] for [ReduceBrightColorsTile.TILE_SPEC].
*
* It will send a signal to add the tile when reduce bright colors is enabled.
*/
@SysUISingleton
class ReduceBrightColorsAutoAddable
@Inject
constructor(
controller: ReduceBrightColorsController,
@Named(RBC_AVAILABLE) private val available: Boolean,
) :
CallbackControllerAutoAddable<
ReduceBrightColorsController.Listener, ReduceBrightColorsController
>(controller) {
override val spec: TileSpec
get() = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
return object : ReduceBrightColorsController.Listener {
override fun onActivated(activated: Boolean) {
if (activated) {
sendAdd()
}
}
}
}
override val autoAddTracking
get() =
if (available) {
super.autoAddTracking
} else {
AutoAddTracking.Disabled
}
override val description = "ReduceBrightColorsAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,94 @@
/*
* 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.pipeline.domain.autoaddable
import android.content.ComponentName
import android.content.pm.PackageManager
import android.content.res.Resources
import android.text.TextUtils
import com.android.systemui.R
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.statusbar.policy.SafetyController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
/**
* [AutoAddable] for the safety tile.
*
* It will send a signal to add the tile when the feature is enabled, indicating the component
* corresponding to the tile. If the feature is disabled, it will send a signal to remove the tile.
*/
@SysUISingleton
class SafetyCenterAutoAddable
@Inject
constructor(
private val safetyController: SafetyController,
private val packageManager: PackageManager,
@Main private val resources: Resources,
@Background private val bgDispatcher: CoroutineDispatcher,
) : AutoAddable {
private suspend fun getSpec(): TileSpec? {
val specClass = resources.getString(R.string.safety_quick_settings_tile_class)
return if (TextUtils.isEmpty(specClass)) {
null
} else {
val packageName =
withContext(bgDispatcher) { packageManager.permissionControllerPackageName }
TileSpec.create(ComponentName(packageName, specClass))
}
}
override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return conflatedCallbackFlow {
val spec = getSpec()
if (spec != null) {
// If not added, we always try to add it
trySend(AutoAddSignal.Add(spec))
val listener =
SafetyController.Listener { isSafetyCenterEnabled ->
if (isSafetyCenterEnabled) {
trySend(AutoAddSignal.Add(spec))
} else {
trySend(AutoAddSignal.Remove(spec))
}
}
safetyController.addCallback(listener)
awaitClose { safetyController.removeCallback(listener) }
} else {
awaitClose {}
}
}
}
override val autoAddTracking: AutoAddTracking
get() = AutoAddTracking.Always
override val description = "SafetyCenterAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,57 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.QuickAccessWalletTile
import com.android.systemui.statusbar.policy.WalletController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
/**
* [AutoAddable] for [QuickAccessWalletTile.TILE_SPEC].
*
* It will always try to add the tile if [WalletController.getWalletPosition] is non-null.
*/
@SysUISingleton
class WalletAutoAddable
@Inject
constructor(
private val walletController: WalletController,
) : AutoAddable {
private val spec = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return flow {
val position = walletController.getWalletPosition()
if (position != null) {
emit(AutoAddSignal.Add(spec, position))
}
}
}
override val autoAddTracking: AutoAddTracking
get() = AutoAddTracking.IfNotAdded(spec)
override val description = "WalletAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,73 @@
/*
* 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.pipeline.domain.autoaddable
import android.content.pm.UserInfo
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.WorkModeTile
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
/**
* [AutoAddable] for [WorkModeTile.TILE_SPEC].
*
* It will send a signal to add the tile when there is a managed profile for the current user, and a
* signal to remove it if there is not.
*/
@SysUISingleton
class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable {
private val spec = TileSpec.create(WorkModeTile.TILE_SPEC)
override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return conflatedCallbackFlow {
fun maybeSend(profiles: List<UserInfo>) {
if (profiles.any { it.id == userId }) {
// We are looking at the profiles of the correct user.
if (profiles.any { it.isManagedProfile }) {
trySend(AutoAddSignal.Add(spec))
} else {
trySend(AutoAddSignal.Remove(spec))
}
}
}
val callback =
object : UserTracker.Callback {
override fun onProfilesChanged(profiles: List<UserInfo>) {
maybeSend(profiles)
}
}
userTracker.addCallback(callback) { it.run() }
maybeSend(userTracker.userProfiles)
awaitClose { userTracker.removeCallback(callback) }
}
}
override val autoAddTracking = AutoAddTracking.Always
override val description = "WorkTileAutoAddable ($autoAddTracking)"
}

View File

@@ -0,0 +1,37 @@
/*
* 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.pipeline.domain.model
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
/** Signal indicating when a tile needs to be auto-added or removed */
sealed interface AutoAddSignal {
/** Tile for this object */
val spec: TileSpec
/** Signal for auto-adding a tile at [position]. */
data class Add(
override val spec: TileSpec,
val position: Int = POSITION_AT_END,
) : AutoAddSignal
/** Signal for removing a tile. */
data class Remove(
override val spec: TileSpec,
) : AutoAddSignal
}

View File

@@ -0,0 +1,49 @@
/*
* 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.pipeline.domain.model
import com.android.systemui.qs.pipeline.shared.TileSpec
/** Strategy for when to track a particular [AutoAddable]. */
sealed interface AutoAddTracking {
/**
* Indicates that the signals from the associated [AutoAddable] should all be collected and
* reacted accordingly. It may have [AutoAddSignal.Add] and [AutoAddSignal.Remove].
*/
object Always : AutoAddTracking {
override fun toString(): String {
return "Always"
}
}
/**
* Indicates that the associated [AutoAddable] is [Disabled] and doesn't need to be collected.
*/
object Disabled : AutoAddTracking {
override fun toString(): String {
return "Disabled"
}
}
/**
* Only the first [AutoAddSignal.Add] for each flow of signals needs to be collected, and only
* if the tile hasn't been auto-added yet. The associated [AutoAddable] will only emit
* [AutoAddSignal.Add].
*/
data class IfNotAdded(val spec: TileSpec) : AutoAddTracking
}

View File

@@ -0,0 +1,47 @@
/*
* 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.pipeline.domain.model
import kotlinx.coroutines.flow.Flow
/**
* Tracks conditions for auto-adding or removing specific tiles.
*
* When creating a new [AutoAddable], it needs to be registered in a [Module] like
* [BaseAutoAddableModule], for example:
* ```
* @Binds
* @IntoSet
* fun providesMyAutoAddable(autoAddable: MyAutoAddable): AutoAddable
* ```
*/
interface AutoAddable {
/**
* Signals associated with a particular user indicating whether a particular tile needs to be
* auto-added or auto-removed.
*/
fun autoAddSignal(userId: Int): Flow<AutoAddSignal>
/**
* Lifecycle for this object. It indicates in which cases [autoAddSignal] should be collected
*/
val autoAddTracking: AutoAddTracking
/** Human readable description */
val description: String
}

View File

@@ -52,7 +52,11 @@ sealed class TileSpec private constructor(open val spec: String) {
internal constructor(
override val spec: String,
val componentName: ComponentName,
) : TileSpec(spec)
) : TileSpec(spec) {
override fun toString(): String {
return "CustomTileSpec(${componentName.toShortString()})"
}
}
companion object {
/** Create a [TileSpec] from the string [spec]. */

View File

@@ -0,0 +1,99 @@
/*
* 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.pipeline.domain.autoaddable
import android.content.ComponentName
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class AutoAddableSettingListTest : SysuiTestCase() {
private val factory =
object : AutoAddableSetting.Factory {
override fun create(setting: String, spec: TileSpec): AutoAddableSetting {
return AutoAddableSetting(
mock(),
mock(),
setting,
spec,
)
}
}
@Test
fun correctLines_correctAutoAddables() {
val setting1 = "setting1"
val setting2 = "setting2"
val spec1 = TileSpec.create("spec1")
val spec2 = TileSpec.create(ComponentName("pkg", "cls"))
context.orCreateTestableResources.addOverride(
R.array.config_quickSettingsAutoAdd,
arrayOf(toStringLine(setting1, spec1), toStringLine(setting2, spec2))
)
val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
assertThat(autoAddables)
.containsExactly(factory.create(setting1, spec1), factory.create(setting2, spec2))
}
@Test
fun malformedLine_ignored() {
val setting = "setting"
val spec = TileSpec.create("spec")
context.orCreateTestableResources.addOverride(
R.array.config_quickSettingsAutoAdd,
arrayOf(toStringLine(setting, spec), "bad_line")
)
val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
assertThat(autoAddables).containsExactly(factory.create(setting, spec))
}
@Test
fun invalidSpec_ignored() {
val setting = "setting"
val spec = TileSpec.create("spec")
context.orCreateTestableResources.addOverride(
R.array.config_quickSettingsAutoAdd,
arrayOf(toStringLine(setting, spec), "invalid:")
)
val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
assertThat(autoAddables).containsExactly(factory.create(setting, spec))
}
companion object {
private fun toStringLine(setting: String, spec: TileSpec) =
"$setting$SETTINGS_SEPARATOR${spec.spec}"
private const val SETTINGS_SEPARATOR = ":"
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
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.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class AutoAddableSettingTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val secureSettings = FakeSettings()
private val underTest =
AutoAddableSetting(
secureSettings,
testDispatcher,
SETTING,
SPEC,
)
@Test
fun settingNotSet_noSignal() =
testScope.runTest {
val userId = 0
val signal by collectLastValue(underTest.autoAddSignal(userId))
assertThat(signal).isNull() // null means no emitted value
}
@Test
fun settingSetTo0_noSignal() =
testScope.runTest {
val userId = 0
val signal by collectLastValue(underTest.autoAddSignal(userId))
secureSettings.putIntForUser(SETTING, 0, userId)
assertThat(signal).isNull() // null means no emitted value
}
@Test
fun settingSetToNon0_signal() =
testScope.runTest {
val userId = 0
val signal by collectLastValue(underTest.autoAddSignal(userId))
secureSettings.putIntForUser(SETTING, 42, userId)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun settingSetForUser_onlySignalInThatUser() =
testScope.runTest {
val signal0 by collectLastValue(underTest.autoAddSignal(0))
val signal1 by collectLastValue(underTest.autoAddSignal(1))
secureSettings.putIntForUser(SETTING, /* value */ 42, /* userHandle */ 1)
assertThat(signal0).isNull()
assertThat(signal1).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun multipleNonZeroChanges_onlyOneSignal() =
testScope.runTest {
val userId = 0
val signals by collectValues(underTest.autoAddSignal(userId))
secureSettings.putIntForUser(SETTING, 1, userId)
secureSettings.putIntForUser(SETTING, 2, userId)
assertThat(signals.size).isEqualTo(1)
}
@Test
fun strategyIfNotAdded() {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
}
companion object {
private const val SETTING = "setting"
private val SPEC = TileSpec.create("spec")
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.statusbar.policy.CallbackController
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class CallbackControllerAutoAddableTest : SysuiTestCase() {
@Test
fun callbackAddedAndRemoved() = runTest {
val controller = TestableController()
val callback = object : TestableController.Callback {}
val underTest =
object :
CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
controller
) {
override val description: String = ""
override val spec: TileSpec
get() = SPEC
override fun ProducerScope<AutoAddSignal>.getCallback():
TestableController.Callback {
return callback
}
}
val job = launch { underTest.autoAddSignal(0).collect {} }
runCurrent()
assertThat(controller.callbacks).containsExactly(callback)
job.cancel()
runCurrent()
assertThat(controller.callbacks).isEmpty()
}
@Test
fun sendAddFromCallback() = runTest {
val controller = TestableController()
val underTest =
object :
CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
controller
) {
override val description: String = ""
override val spec: TileSpec
get() = SPEC
override fun ProducerScope<AutoAddSignal>.getCallback():
TestableController.Callback {
return object : TestableController.Callback {
override fun change() {
sendAdd()
}
}
}
}
val signal by collectLastValue(underTest.autoAddSignal(0))
assertThat(signal).isNull()
controller.callbacks.first().change()
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun strategyIfNotAdded() {
val underTest =
object :
CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
TestableController()
) {
override val description: String = ""
override val spec: TileSpec
get() = SPEC
override fun ProducerScope<AutoAddSignal>.getCallback():
TestableController.Callback {
return object : TestableController.Callback {}
}
}
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
}
private class TestableController : CallbackController<TestableController.Callback> {
val callbacks = mutableSetOf<Callback>()
override fun addCallback(listener: Callback) {
callbacks.add(listener)
}
override fun removeCallback(listener: Callback) {
callbacks.remove(listener)
}
interface Callback {
fun change() {}
}
}
companion object {
private val SPEC = TileSpec.create("test")
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.CastTile
import com.android.systemui.statusbar.policy.CastController
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class CastAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var castController: CastController
@Captor private lateinit var callbackCaptor: ArgumentCaptor<CastController.Callback>
private lateinit var underTest: CastAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = CastAutoAddable(castController)
}
@Test
fun onCastDevicesChanged_noDevices_noSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(castController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onCastDevicesChanged()
assertThat(signal).isNull()
}
@Test
fun onCastDevicesChanged_deviceNotConnectedOrConnecting_noSignal() = runTest {
val device =
CastController.CastDevice().apply {
state = CastController.CastDevice.STATE_DISCONNECTED
}
whenever(castController.castDevices).thenReturn(listOf(device))
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(castController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onCastDevicesChanged()
assertThat(signal).isNull()
}
@Test
fun onCastDevicesChanged_someDeviceConnecting_addSignal() = runTest {
val disconnectedDevice =
CastController.CastDevice().apply {
state = CastController.CastDevice.STATE_DISCONNECTED
}
val connectingDevice =
CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTING }
whenever(castController.castDevices)
.thenReturn(listOf(disconnectedDevice, connectingDevice))
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(castController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onCastDevicesChanged()
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun onCastDevicesChanged_someDeviceConnected_addSignal() = runTest {
val disconnectedDevice =
CastController.CastDevice().apply {
state = CastController.CastDevice.STATE_DISCONNECTED
}
val connectedDevice =
CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTED }
whenever(castController.castDevices).thenReturn(listOf(disconnectedDevice, connectedDevice))
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(castController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onCastDevicesChanged()
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
companion object {
private val SPEC = TileSpec.create(CastTile.TILE_SPEC)
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.DataSaverTile
import com.android.systemui.statusbar.policy.DataSaverController
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DataSaverAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var dataSaverController: DataSaverController
@Captor private lateinit var callbackCaptor: ArgumentCaptor<DataSaverController.Listener>
private lateinit var underTest: DataSaverAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = DataSaverAutoAddable(dataSaverController)
}
@Test
fun dataSaverNotEnabled_NoSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(dataSaverController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onDataSaverChanged(false)
assertThat(signal).isNull()
}
@Test
fun dataSaverEnabled_addSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(dataSaverController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onDataSaverChanged(true)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
companion object {
private val SPEC = TileSpec.create(DataSaverTile.TILE_SPEC)
}
}

View File

@@ -0,0 +1,115 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.DeviceControlsTile
import com.android.systemui.statusbar.policy.DeviceControlsController
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DeviceControlsAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var deviceControlsController: DeviceControlsController
@Captor private lateinit var callbackCaptor: ArgumentCaptor<DeviceControlsController.Callback>
private lateinit var underTest: DeviceControlsAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = DeviceControlsAutoAddable(deviceControlsController)
}
@Test
fun strategyAlways() {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
}
@Test
fun onControlsUpdate_position_addSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
val position = 5
runCurrent()
verify(deviceControlsController).setCallback(capture(callbackCaptor))
callbackCaptor.value.onControlsUpdate(position)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
verify(deviceControlsController).removeCallback()
}
@Test
fun onControlsUpdate_nullPosition_noAddSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(deviceControlsController).setCallback(capture(callbackCaptor))
callbackCaptor.value.onControlsUpdate(null)
assertThat(signal).isNull()
verify(deviceControlsController).removeCallback()
}
@Test
fun onRemoveControlsAutoTracker_removeSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(deviceControlsController).setCallback(capture(callbackCaptor))
callbackCaptor.value.removeControlsAutoTracker()
assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
}
@Test
fun flowCancelled_removeCallback() = runTest {
val job = launch { underTest.autoAddSignal(0).collect() }
runCurrent()
job.cancel()
runCurrent()
verify(deviceControlsController).removeCallback()
}
companion object {
private val SPEC = TileSpec.create(DeviceControlsTile.TILE_SPEC)
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.HotspotTile
import com.android.systemui.statusbar.policy.HotspotController
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class HotspotAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var hotspotController: HotspotController
@Captor private lateinit var callbackCaptor: ArgumentCaptor<HotspotController.Callback>
private lateinit var underTest: HotspotAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = HotspotAutoAddable(hotspotController)
}
@Test
fun enabled_addSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(hotspotController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onHotspotChanged(/* enabled = */ true, /* numDevices = */ 5)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun notEnabled_noSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(hotspotController).addCallback(capture(callbackCaptor))
callbackCaptor.value.onHotspotChanged(/* enabled = */ false, /* numDevices = */ 0)
assertThat(signal).isNull()
}
companion object {
private val SPEC = TileSpec.create(HotspotTile.TILE_SPEC)
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.pipeline.domain.autoaddable
import android.hardware.display.NightDisplayListener
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dagger.NightDisplayListenerModule
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.NightDisplayTile
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class NightDisplayAutoAddableTest : SysuiTestCase() {
@Mock(answer = Answers.RETURNS_SELF)
private lateinit var nightDisplayListenerBuilder: NightDisplayListenerModule.Builder
@Mock private lateinit var nightDisplayListener: NightDisplayListener
@Captor private lateinit var callbackCaptor: ArgumentCaptor<NightDisplayListener.Callback>
private lateinit var underTest: NightDisplayAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(nightDisplayListenerBuilder.build()).thenReturn(nightDisplayListener)
}
@Test
fun disabled_strategyDisabled() =
testWithFeatureAvailability(enabled = false) {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
}
@Test
fun enabled_strategyIfNotAdded() = testWithFeatureAvailability {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
}
@Test
fun listenerCreatedForCorrectUser() = testWithFeatureAvailability {
val user = 42
backgroundScope.launch { underTest.autoAddSignal(user).collect() }
runCurrent()
val inOrder = inOrder(nightDisplayListenerBuilder)
inOrder.verify(nightDisplayListenerBuilder).setUser(user)
inOrder.verify(nightDisplayListenerBuilder).build()
}
@Test
fun onCancelFlow_removeCallback() = testWithFeatureAvailability {
val job = launch { underTest.autoAddSignal(0).collect() }
runCurrent()
job.cancel()
runCurrent()
verify(nightDisplayListener).setCallback(null)
}
@Test
fun onActivatedTrue_addSignal() = testWithFeatureAvailability {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(nightDisplayListener).setCallback(capture(callbackCaptor))
callbackCaptor.value.onActivated(true)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
private fun testWithFeatureAvailability(
enabled: Boolean = true,
body: suspend TestScope.() -> TestResult
) = runTest {
context.orCreateTestableResources.addOverride(
com.android.internal.R.bool.config_nightDisplayAvailable,
enabled
)
underTest = NightDisplayAutoAddable(nightDisplayListenerBuilder, context)
body()
}
companion object {
private val SPEC = TileSpec.create(NightDisplayTile.TILE_SPEC)
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.ReduceBrightColorsController
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.ReduceBrightColorsTile
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
@Captor
private lateinit var reduceBrightColorsListenerCaptor:
ArgumentCaptor<ReduceBrightColorsController.Listener>
private lateinit var underTest: ReduceBrightColorsAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@Test
fun notAvailable_strategyDisabled() =
testWithFeatureAvailability(available = false) {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
}
@Test
fun available_strategyIfNotAdded() =
testWithFeatureAvailability(available = true) {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
}
@Test
fun activated_addSignal() = testWithFeatureAvailability {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
reduceBrightColorsListenerCaptor.value.onActivated(true)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun notActivated_noSignal() = testWithFeatureAvailability {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
reduceBrightColorsListenerCaptor.value.onActivated(false)
assertThat(signal).isNull()
}
private fun testWithFeatureAvailability(
available: Boolean = true,
body: suspend TestScope.() -> TestResult
) = runTest {
underTest = ReduceBrightColorsAutoAddable(reduceBrightColorsController, available)
body()
}
companion object {
private val SPEC = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
}
}

View File

@@ -0,0 +1,163 @@
/*
* 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.pipeline.domain.autoaddable
import android.content.ComponentName
import android.content.pm.PackageManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.statusbar.policy.SafetyController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class SafetyCenterAutoAddableTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Mock private lateinit var safetyController: SafetyController
@Mock private lateinit var packageManager: PackageManager
@Captor
private lateinit var safetyControllerListenerCaptor: ArgumentCaptor<SafetyController.Listener>
private lateinit var underTest: SafetyCenterAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context.ensureTestableResources()
// Set these by default, will also test special cases
context.orCreateTestableResources.addOverride(
R.string.safety_quick_settings_tile_class,
SAFETY_TILE_CLASS_NAME
)
whenever(packageManager.permissionControllerPackageName)
.thenReturn(PERMISSION_CONTROLLER_PACKAGE_NAME)
underTest =
SafetyCenterAutoAddable(
safetyController,
packageManager,
context.resources,
testDispatcher,
)
}
@Test
fun strategyAlwaysTrack() =
testScope.runTest {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
}
@Test
fun tileAlwaysAdded() =
testScope.runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun safetyCenterDisabled_removeSignal() =
testScope.runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(false)
assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
}
@Test
fun safetyCenterEnabled_newAddSignal() =
testScope.runTest {
val signals by collectValues(underTest.autoAddSignal(0))
runCurrent()
verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(true)
assertThat(signals.size).isEqualTo(2)
assertThat(signals.last()).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun flowCancelled_removeListener() =
testScope.runTest {
val job = launch { underTest.autoAddSignal(0).collect() }
runCurrent()
verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
job.cancel()
runCurrent()
verify(safetyController).removeCallback(safetyControllerListenerCaptor.value)
}
@Test
fun emptyClassName_noSignals() =
testScope.runTest {
context.orCreateTestableResources.addOverride(
R.string.safety_quick_settings_tile_class,
""
)
val signal by collectLastValue(underTest.autoAddSignal(0))
runCurrent()
verify(safetyController, never()).addCallback(any())
assertThat(signal).isNull()
}
companion object {
private const val SAFETY_TILE_CLASS_NAME = "cls"
private const val PERMISSION_CONTROLLER_PACKAGE_NAME = "pkg"
private val SPEC =
TileSpec.create(
ComponentName(PERMISSION_CONTROLLER_PACKAGE_NAME, SAFETY_TILE_CLASS_NAME)
)
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.pipeline.domain.autoaddable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.QuickAccessWalletTile
import com.android.systemui.statusbar.policy.WalletController
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class WalletAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var walletController: WalletController
private lateinit var underTest: WalletAutoAddable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = WalletAutoAddable(walletController)
}
@Test
fun strategyIfNotAdded() {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
}
@Test
fun walletPositionNull_noSignal() = runTest {
whenever(walletController.getWalletPosition()).thenReturn(null)
val signal by collectLastValue(underTest.autoAddSignal(0))
assertThat(signal).isNull()
}
@Test
fun walletPositionNumber_addedInThatPosition() = runTest {
val position = 4
whenever(walletController.getWalletPosition()).thenReturn(4)
val signal by collectLastValue(underTest.autoAddSignal(0))
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
}
companion object {
private val SPEC = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.pipeline.domain.autoaddable
import android.content.pm.UserInfo
import android.content.pm.UserInfo.FLAG_FULL
import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
import android.content.pm.UserInfo.FLAG_PRIMARY
import android.content.pm.UserInfo.FLAG_PROFILE
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.WorkModeTile
import com.android.systemui.settings.FakeUserTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class WorkTileAutoAddableTest : SysuiTestCase() {
private lateinit var userTracker: FakeUserTracker
private lateinit var underTest: WorkTileAutoAddable
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
userTracker =
FakeUserTracker(
_userId = USER_INFO_0.id,
_userInfo = USER_INFO_0,
_userProfiles = listOf(USER_INFO_0)
)
underTest = WorkTileAutoAddable(userTracker)
}
@Test
fun changeInProfiles_hasManagedProfile_sendsAddSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun changeInProfiles_noManagedProfile_sendsRemoveSignal() = runTest {
userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
val signal by collectLastValue(underTest.autoAddSignal(0))
userTracker.set(listOf(USER_INFO_0), selectedUserIndex = 0)
assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
}
@Test
fun startingWithManagedProfile_sendsAddSignal() = runTest {
userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
val signal by collectLastValue(underTest.autoAddSignal(0))
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun userChangeToUserWithProfile_noSignalForOriginalUser() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
userTracker.set(listOf(USER_INFO_1, USER_INFO_WORK), selectedUserIndex = 0)
assertThat(signal).isNotEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun userChangeToUserWithoutProfile_noSignalForOriginalUser() = runTest {
userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
val signal by collectLastValue(underTest.autoAddSignal(0))
userTracker.set(listOf(USER_INFO_1), selectedUserIndex = 0)
assertThat(signal).isNotEqualTo(AutoAddSignal.Remove(SPEC))
}
@Test
fun strategyAlways() {
assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
}
companion object {
private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.pipeline.domain.autoaddable
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
class FakeAutoAddable(
private val spec: TileSpec,
override val autoAddTracking: AutoAddTracking,
) : AutoAddable {
private val signalsPerUser = mutableMapOf<Int, MutableStateFlow<AutoAddSignal?>>()
private fun getFlow(userId: Int): MutableStateFlow<AutoAddSignal?> =
signalsPerUser.getOrPut(userId) { MutableStateFlow(null) }
override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
return getFlow(userId).asStateFlow().filterNotNull()
}
suspend fun sendRemoveSignal(userId: Int) {
getFlow(userId).value = AutoAddSignal.Remove(spec)
}
suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
getFlow(userId).value = AutoAddSignal.Add(spec, position)
}
override val description: String
get() = "FakeAutoAddable($spec)"
}