Merge changes from topic "278577795" into udc-qpr-dev

* changes:
  Use a CoreStartable to start AutoAddInteractor
  Add a AutoAddInteractor
  Add AutoAddables
This commit is contained in:
Fabian Kozynski
2023-06-26 18:28:24 +00:00
committed by Android (Google) Code Review
43 changed files with 2945 additions and 127 deletions

View File

@@ -161,7 +161,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
// finishes before creating any tiles.
tunerService.addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
mAutoTiles = autoTiles.get();
if (!mFeatureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
mAutoTiles = autoTiles.get();
}
});
}

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,22 @@
/*
* 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 javax.inject.Qualifier
/** A [LogBuffer] for the QS pipeline to track auto-added tiles */
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSAutoAddLog

View File

@@ -16,13 +16,40 @@
package com.android.systemui.qs.pipeline.dagger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
import com.android.systemui.qs.pipeline.data.repository.AutoAddSettingRepository
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.Multibinds
@Module
@Module(
includes =
[
BaseAutoAddableModule::class,
]
)
abstract class QSAutoAddModule {
@Binds abstract fun bindAutoAddRepository(impl: AutoAddSettingRepository): AutoAddRepository
@Multibinds abstract fun providesAutoAddableSet(): Set<AutoAddable>
companion object {
/**
* Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
* auto added tiles.
*/
@Provides
@SysUISingleton
@QSAutoAddLog
fun provideQSAutoAddLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create(QSPipelineLogger.AUTO_ADD_TAG, maxSize = 100, systrace = false)
}
}
}

View File

@@ -26,7 +26,7 @@ import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import dagger.Binds
import dagger.Module
@@ -53,8 +53,8 @@ abstract class QSPipelineModule {
@Binds
@IntoMap
@ClassKey(PrototypeCoreStartable::class)
abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
@ClassKey(QSPipelineCoreStartable::class)
abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
companion object {
/**

View File

@@ -19,5 +19,5 @@ import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import javax.inject.Qualifier
/** A {@link LogBuffer} for the new QS Pipeline for logging changes to the set of current tiles. */
/** A [LogBuffer] for the new QS Pipeline for logging changes to the set of current tiles. */
@Qualifier @MustBeDocumented @Retention(RetentionPolicy.RUNTIME) annotation class QSTileListLog

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,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.interactor
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
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.logging.QSPipelineLogger
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
import java.io.PrintWriter
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
/**
* Collects the signals coming from all registered [AutoAddable] and adds/removes tiles accordingly.
*/
@SysUISingleton
class AutoAddInteractor
@Inject
constructor(
private val autoAddables: Set<@JvmSuppressWildcards AutoAddable>,
private val repository: AutoAddRepository,
private val dumpManager: DumpManager,
private val qsPipelineLogger: QSPipelineLogger,
@Application private val scope: CoroutineScope,
) : Dumpable {
private val initialized = AtomicBoolean(false)
/** Start collection of signals following the user from [currentTilesInteractor]. */
fun init(currentTilesInteractor: CurrentTilesInteractor) {
if (!initialized.compareAndSet(false, true)) {
return
}
dumpManager.registerNormalDumpable(TAG, this)
scope.launch {
currentTilesInteractor.userId.collectLatest { userId ->
coroutineScope {
val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
autoAddables
.map { addable ->
val autoAddSignal = addable.autoAddSignal(userId)
when (val lifecycle = addable.autoAddTracking) {
is AutoAddTracking.Always -> autoAddSignal
is AutoAddTracking.Disabled -> emptyFlow()
is AutoAddTracking.IfNotAdded -> {
if (lifecycle.spec !in previouslyAdded.value) {
autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
} else {
emptyFlow()
}
}
}
}
.merge()
.collect { signal ->
when (signal) {
is AutoAddSignal.Add -> {
if (signal.spec !in previouslyAdded.value) {
currentTilesInteractor.addTile(signal.spec, signal.position)
qsPipelineLogger.logTileAutoAdded(
userId,
signal.spec,
signal.position
)
repository.markTileAdded(userId, signal.spec)
}
}
is AutoAddSignal.Remove -> {
currentTilesInteractor.removeTiles(setOf(signal.spec))
qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
repository.unmarkTileAdded(userId, signal.spec)
}
}
}
}
}
}
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
with(pw.asIndenting()) {
println("AutoAddables:")
indentIfPossible { autoAddables.forEach { println(it.description) } }
}
}
companion object {
private const val TAG = "AutoAddInteractor"
}
}

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

@@ -0,0 +1,44 @@
/*
* 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.startable
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import javax.inject.Inject
@SysUISingleton
class QSPipelineCoreStartable
@Inject
constructor(
private val currentTilesInteractor: CurrentTilesInteractor,
private val autoAddInteractor: AutoAddInteractor,
private val featureFlags: FeatureFlags,
) : CoreStartable {
override fun start() {
if (
featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) &&
featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
) {
autoAddInteractor.init(currentTilesInteractor)
}
}
}

View File

@@ -1,120 +0,0 @@
/*
* 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.prototyping
import android.util.Log
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
/**
* Class for observing results while prototyping.
*
* The flows do their own logging, so we just need to make sure that they collect.
*
* This will be torn down together with the last of the new pipeline flags remaining here.
*/
// TODO(b/270385608)
@SysUISingleton
class PrototypeCoreStartable
@Inject
constructor(
private val tileSpecRepository: TileSpecRepository,
private val autoAddRepository: AutoAddRepository,
private val userRepository: UserRepository,
private val featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
private val commandRegistry: CommandRegistry,
) : CoreStartable {
@OptIn(ExperimentalCoroutinesApi::class)
override fun start() {
if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
scope.launch {
userRepository.selectedUserInfo
.flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) }
.collect {}
}
if (featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
scope.launch {
userRepository.selectedUserInfo
.flatMapLatest { user -> autoAddRepository.autoAddedTiles(user.id) }
.collect { tiles -> Log.d(TAG, "Auto-added tiles: $tiles") }
}
}
commandRegistry.registerCommand(COMMAND, ::CommandExecutor)
}
}
private inner class CommandExecutor : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.size < 2) {
pw.println("Error: needs at least two arguments")
return
}
val spec = TileSpec.create(args[1])
if (spec == TileSpec.Invalid) {
pw.println("Error: Invalid tile spec ${args[1]}")
}
if (args[0] == "add") {
performAdd(args, spec)
pw.println("Requested tile added")
} else if (args[0] == "remove") {
performRemove(args, spec)
pw.println("Requested tile removed")
} else {
pw.println("Error: unknown command")
}
}
private fun performAdd(args: List<String>, spec: TileSpec) {
val position = args.getOrNull(2)?.toInt() ?: TileSpecRepository.POSITION_AT_END
val user = args.getOrNull(3)?.toInt() ?: userRepository.getSelectedUserInfo().id
scope.launch { tileSpecRepository.addTile(user, spec, position) }
}
private fun performRemove(args: List<String>, spec: TileSpec) {
val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
}
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $COMMAND:")
pw.println(" add <spec> [position] [user]")
pw.println(" remove <spec> [user]")
}
}
companion object {
private const val COMMAND = "qs-pipeline"
private const val TAG = "PrototypeCoreStartable"
}
}

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

@@ -19,6 +19,7 @@ package com.android.systemui.qs.pipeline.shared.logging
import android.annotation.UserIdInt
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog
import com.android.systemui.qs.pipeline.dagger.QSTileListLog
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -32,10 +33,12 @@ class QSPipelineLogger
@Inject
constructor(
@QSTileListLog private val tileListLogBuffer: LogBuffer,
@QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer,
) {
companion object {
const val TILE_LIST_TAG = "QSTileListLog"
const val AUTO_ADD_TAG = "QSAutoAddableLog"
}
/**
@@ -136,6 +139,31 @@ constructor(
)
}
fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) {
tileAutoAddLogBuffer.log(
AUTO_ADD_TAG,
LogLevel.DEBUG,
{
int1 = userId
int2 = position
str1 = spec.toString()
},
{ "Tile $str1 auto added for user $int1 at position $int2" }
)
}
fun logTileAutoRemoved(userId: Int, spec: TileSpec) {
tileAutoAddLogBuffer.log(
AUTO_ADD_TAG,
LogLevel.DEBUG,
{
int1 = userId
str1 = spec.toString()
},
{ "Tile $str1 auto removed for user $int1" }
)
}
/** Reasons for destroying an existing tile. */
enum class TileDestroyedReason(val readable: String) {
TILE_REMOVED("Tile removed from current set"),

View File

@@ -140,6 +140,7 @@ public class QSTileHostTest extends SysuiTestCase {
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
mMainExecutor = new FakeExecutor(new FakeSystemClock());

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,201 @@
/*
* 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.interactor
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
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.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
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.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
class AutoAddInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
private val autoAddRepository = FakeAutoAddRepository()
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var currentTilesInteractor: CurrentTilesInteractor
@Mock private lateinit var logger: QSPipelineLogger
private lateinit var underTest: AutoAddInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(currentTilesInteractor.userId).thenReturn(MutableStateFlow(USER))
}
@Test
fun autoAddable_alwaysTrack_addSignal_tileAddedAndMarked() =
testScope.runTest {
val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
underTest = createInteractor(setOf(fakeAutoAddable))
val position = 3
fakeAutoAddable.sendAddSignal(USER, position)
runCurrent()
verify(currentTilesInteractor).addTile(SPEC, position)
assertThat(autoAddedTiles).contains(SPEC)
}
@Test
fun autoAddable_alwaysTrack_addThenRemoveSignal_tileAddedAndRemoved() =
testScope.runTest {
val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
underTest = createInteractor(setOf(fakeAutoAddable))
val position = 3
fakeAutoAddable.sendAddSignal(USER, position)
runCurrent()
fakeAutoAddable.sendRemoveSignal(USER)
runCurrent()
val inOrder = inOrder(currentTilesInteractor)
inOrder.verify(currentTilesInteractor).addTile(SPEC, position)
inOrder.verify(currentTilesInteractor).removeTiles(setOf(SPEC))
assertThat(autoAddedTiles).doesNotContain(SPEC)
}
@Test
fun autoAddable_alwaysTrack_addSignalWhenAddedPreviously_noop() =
testScope.runTest {
val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
autoAddRepository.markTileAdded(USER, SPEC)
runCurrent()
underTest = createInteractor(setOf(fakeAutoAddable))
val position = 3
fakeAutoAddable.sendAddSignal(USER, position)
runCurrent()
verify(currentTilesInteractor, never()).addTile(SPEC, position)
}
@Test
fun autoAddable_disabled_noInteractionsWithCurrentTilesInteractor() =
testScope.runTest {
val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Disabled)
val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
underTest = createInteractor(setOf(fakeAutoAddable))
val position = 3
fakeAutoAddable.sendAddSignal(USER, position)
runCurrent()
fakeAutoAddable.sendRemoveSignal(USER)
runCurrent()
verify(currentTilesInteractor, never()).addTile(any(), anyInt())
verify(currentTilesInteractor, never()).removeTiles(any())
assertThat(autoAddedTiles).doesNotContain(SPEC)
}
@Test
fun autoAddable_trackIfNotAdded_removeSignal_noop() =
testScope.runTest {
val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
runCurrent()
underTest = createInteractor(setOf(fakeAutoAddable))
fakeAutoAddable.sendRemoveSignal(USER)
runCurrent()
verify(currentTilesInteractor, never()).addTile(any(), anyInt())
verify(currentTilesInteractor, never()).removeTiles(any())
}
@Test
fun autoAddable_trackIfNotAdded_addSignalWhenPreviouslyAdded_noop() =
testScope.runTest {
val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
autoAddRepository.markTileAdded(USER, SPEC)
runCurrent()
underTest = createInteractor(setOf(fakeAutoAddable))
fakeAutoAddable.sendAddSignal(USER)
runCurrent()
verify(currentTilesInteractor, never()).addTile(any(), anyInt())
verify(currentTilesInteractor, never()).removeTiles(any())
}
@Test
fun autoAddable_trackIfNotAdded_addSignal_addedAndMarked() =
testScope.runTest {
val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
underTest = createInteractor(setOf(fakeAutoAddable))
val position = 3
fakeAutoAddable.sendAddSignal(USER, position)
runCurrent()
verify(currentTilesInteractor).addTile(SPEC, position)
assertThat(autoAddedTiles).contains(SPEC)
}
private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
return AutoAddInteractor(
autoAddables,
autoAddRepository,
dumpManager,
logger,
testScope.backgroundScope
)
.apply { init(currentTilesInteractor) }
}
companion object {
private val SPEC = TileSpec.create("spec")
private val USER = 10
}
}

View File

@@ -100,6 +100,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))

View File

@@ -0,0 +1,42 @@
/*
* 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.data.repository
import com.android.systemui.qs.pipeline.shared.TileSpec
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeAutoAddRepository : AutoAddRepository {
private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>()
override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
return getFlow(userId)
}
override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
if (spec == TileSpec.Invalid) return
with(getFlow(userId)) { value = value.toMutableSet().apply { add(spec) } }
}
override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
with(getFlow(userId)) { value = value.toMutableSet().apply { remove(spec) } }
}
private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> =
autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
}

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)"
}