Merge changes from topic "demo-mode-flow" into tm-qpr-dev
* changes: [Sb refactor] Wifi demo mode [Sb refactor] Add a demo command flow method
This commit is contained in:
@@ -20,7 +20,7 @@ import android.content.Context
|
|||||||
import android.database.ContentObserver
|
import android.database.ContentObserver
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.Settings
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to track the availability of [DemoMode]. Use this class to track the availability and
|
* Class to track the availability of [DemoMode]. Use this class to track the availability and
|
||||||
@@ -29,7 +29,10 @@ import android.provider.Settings
|
|||||||
* This class works by wrapping a content observer for the relevant keys related to DemoMode
|
* This class works by wrapping a content observer for the relevant keys related to DemoMode
|
||||||
* availability and current on/off state, and triggering callbacks.
|
* availability and current on/off state, and triggering callbacks.
|
||||||
*/
|
*/
|
||||||
abstract class DemoModeAvailabilityTracker(val context: Context) {
|
abstract class DemoModeAvailabilityTracker(
|
||||||
|
val context: Context,
|
||||||
|
val globalSettings: GlobalSettings,
|
||||||
|
) {
|
||||||
var isInDemoMode = false
|
var isInDemoMode = false
|
||||||
var isDemoModeAvailable = false
|
var isDemoModeAvailable = false
|
||||||
|
|
||||||
@@ -41,9 +44,9 @@ abstract class DemoModeAvailabilityTracker(val context: Context) {
|
|||||||
fun startTracking() {
|
fun startTracking() {
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
resolver.registerContentObserver(
|
resolver.registerContentObserver(
|
||||||
Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
|
globalSettings.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
|
||||||
resolver.registerContentObserver(
|
resolver.registerContentObserver(
|
||||||
Settings.Global.getUriFor(DEMO_MODE_ON), false, onObserver)
|
globalSettings.getUriFor(DEMO_MODE_ON), false, onObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopTracking() {
|
fun stopTracking() {
|
||||||
@@ -57,12 +60,11 @@ abstract class DemoModeAvailabilityTracker(val context: Context) {
|
|||||||
abstract fun onDemoModeFinished()
|
abstract fun onDemoModeFinished()
|
||||||
|
|
||||||
private fun checkIsDemoModeAllowed(): Boolean {
|
private fun checkIsDemoModeAllowed(): Boolean {
|
||||||
return Settings.Global
|
return globalSettings.getInt(DEMO_MODE_ALLOWED, 0) != 0
|
||||||
.getInt(context.contentResolver, DEMO_MODE_ALLOWED, 0) != 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkIsDemoModeOn(): Boolean {
|
private fun checkIsDemoModeOn(): Boolean {
|
||||||
return Settings.Global.getInt(context.contentResolver, DEMO_MODE_ON, 0) != 0
|
return globalSettings.getInt(DEMO_MODE_ON, 0) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private val allowedObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
|
private val allowedObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
|
||||||
|
|||||||
@@ -24,22 +24,28 @@ import android.os.Bundle
|
|||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.systemui.Dumpable
|
import com.android.systemui.Dumpable
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
import com.android.systemui.demomode.DemoMode.ACTION_DEMO
|
import com.android.systemui.demomode.DemoMode.ACTION_DEMO
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.statusbar.policy.CallbackController
|
import com.android.systemui.statusbar.policy.CallbackController
|
||||||
import com.android.systemui.util.Assert
|
import com.android.systemui.util.Assert
|
||||||
import com.android.systemui.util.settings.GlobalSettings
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles system broadcasts for [DemoMode]
|
* Handles system broadcasts for [DemoMode]
|
||||||
*
|
*
|
||||||
* Injected via [DemoModeModule]
|
* Injected via [DemoModeModule]
|
||||||
*/
|
*/
|
||||||
class DemoModeController constructor(
|
class DemoModeController
|
||||||
|
constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val dumpManager: DumpManager,
|
private val dumpManager: DumpManager,
|
||||||
private val globalSettings: GlobalSettings
|
private val globalSettings: GlobalSettings,
|
||||||
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
) : CallbackController<DemoMode>, Dumpable {
|
) : CallbackController<DemoMode>, Dumpable {
|
||||||
|
|
||||||
// Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
|
// Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
|
||||||
@@ -58,9 +64,7 @@ class DemoModeController constructor(
|
|||||||
requestFinishDemoMode()
|
requestFinishDemoMode()
|
||||||
|
|
||||||
val m = mutableMapOf<String, MutableList<DemoMode>>()
|
val m = mutableMapOf<String, MutableList<DemoMode>>()
|
||||||
DemoMode.COMMANDS.map { command ->
|
DemoMode.COMMANDS.map { command -> m.put(command, mutableListOf()) }
|
||||||
m.put(command, mutableListOf())
|
|
||||||
}
|
|
||||||
receiverMap = m
|
receiverMap = m
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +75,7 @@ class DemoModeController constructor(
|
|||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
|
|
||||||
dumpManager.registerDumpable(TAG, this)
|
dumpManager.registerNormalDumpable(TAG, this)
|
||||||
|
|
||||||
// Due to DemoModeFragment running in systemui:tuner process, we have to observe for
|
// Due to DemoModeFragment running in systemui:tuner process, we have to observe for
|
||||||
// content changes to know if the setting turned on or off
|
// content changes to know if the setting turned on or off
|
||||||
@@ -81,8 +85,13 @@ class DemoModeController constructor(
|
|||||||
|
|
||||||
val demoFilter = IntentFilter()
|
val demoFilter = IntentFilter()
|
||||||
demoFilter.addAction(ACTION_DEMO)
|
demoFilter.addAction(ACTION_DEMO)
|
||||||
context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter,
|
|
||||||
android.Manifest.permission.DUMP, null, Context.RECEIVER_EXPORTED)
|
broadcastDispatcher.registerReceiver(
|
||||||
|
receiver = broadcastReceiver,
|
||||||
|
filter = demoFilter,
|
||||||
|
user = UserHandle.ALL,
|
||||||
|
permission = android.Manifest.permission.DUMP,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addCallback(listener: DemoMode) {
|
override fun addCallback(listener: DemoMode) {
|
||||||
@@ -91,16 +100,15 @@ class DemoModeController constructor(
|
|||||||
|
|
||||||
commands.forEach { command ->
|
commands.forEach { command ->
|
||||||
if (!receiverMap.containsKey(command)) {
|
if (!receiverMap.containsKey(command)) {
|
||||||
throw IllegalStateException("Command ($command) not recognized. " +
|
throw IllegalStateException(
|
||||||
"See DemoMode.java for valid commands")
|
"Command ($command) not recognized. " + "See DemoMode.java for valid commands"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
receiverMap[command]!!.add(listener)
|
receiverMap[command]!!.add(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(this) {
|
synchronized(this) { receivers.add(listener) }
|
||||||
receivers.add(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInDemoMode) {
|
if (isInDemoMode) {
|
||||||
listener.onDemoModeStarted()
|
listener.onDemoModeStarted()
|
||||||
@@ -109,14 +117,46 @@ class DemoModeController constructor(
|
|||||||
|
|
||||||
override fun removeCallback(listener: DemoMode) {
|
override fun removeCallback(listener: DemoMode) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
listener.demoCommands().forEach { command ->
|
listener.demoCommands().forEach { command -> receiverMap[command]!!.remove(listener) }
|
||||||
receiverMap[command]!!.remove(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
receivers.remove(listener)
|
receivers.remove(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Flow] for the stream of demo mode arguments that come in for the given [command]
|
||||||
|
*
|
||||||
|
* This is equivalent of creating a listener manually and adding an event handler for the given
|
||||||
|
* command, like so:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* class Demoable {
|
||||||
|
* private val demoHandler = object : DemoMode {
|
||||||
|
* override fun demoCommands() = listOf(<command>)
|
||||||
|
*
|
||||||
|
* override fun dispatchDemoCommand(command: String, args: Bundle) {
|
||||||
|
* handleDemoCommand(args)
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param command The top-level demo mode command you want a stream for
|
||||||
|
*/
|
||||||
|
fun demoFlowForCommand(command: String): Flow<Bundle> = conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : DemoMode {
|
||||||
|
override fun demoCommands(): List<String> = listOf(command)
|
||||||
|
|
||||||
|
override fun dispatchDemoCommand(command: String, args: Bundle) {
|
||||||
|
trySend(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCallback(callback)
|
||||||
|
awaitClose { removeCallback(callback) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun setIsDemoModeAllowed(enabled: Boolean) {
|
private fun setIsDemoModeAllowed(enabled: Boolean) {
|
||||||
// Turn off demo mode if it was on
|
// Turn off demo mode if it was on
|
||||||
if (isInDemoMode && !enabled) {
|
if (isInDemoMode && !enabled) {
|
||||||
@@ -129,13 +169,9 @@ class DemoModeController constructor(
|
|||||||
Assert.isMainThread()
|
Assert.isMainThread()
|
||||||
|
|
||||||
val copy: List<DemoModeCommandReceiver>
|
val copy: List<DemoModeCommandReceiver>
|
||||||
synchronized(this) {
|
synchronized(this) { copy = receivers.toList() }
|
||||||
copy = receivers.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
copy.forEach { r ->
|
copy.forEach { r -> r.onDemoModeStarted() }
|
||||||
r.onDemoModeStarted()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exitDemoMode() {
|
private fun exitDemoMode() {
|
||||||
@@ -143,18 +179,13 @@ class DemoModeController constructor(
|
|||||||
Assert.isMainThread()
|
Assert.isMainThread()
|
||||||
|
|
||||||
val copy: List<DemoModeCommandReceiver>
|
val copy: List<DemoModeCommandReceiver>
|
||||||
synchronized(this) {
|
synchronized(this) { copy = receivers.toList() }
|
||||||
copy = receivers.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
copy.forEach { r ->
|
copy.forEach { r -> r.onDemoModeFinished() }
|
||||||
r.onDemoModeFinished()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatchDemoCommand(command: String, args: Bundle) {
|
fun dispatchDemoCommand(command: String, args: Bundle) {
|
||||||
Assert.isMainThread()
|
Assert.isMainThread()
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "dispatchDemoCommand: $command, args=$args")
|
Log.d(TAG, "dispatchDemoCommand: $command, args=$args")
|
||||||
}
|
}
|
||||||
@@ -172,9 +203,7 @@ class DemoModeController constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See? demo mode is easy now, you just notify the listeners when their command is called
|
// See? demo mode is easy now, you just notify the listeners when their command is called
|
||||||
receiverMap[command]!!.forEach { receiver ->
|
receiverMap[command]!!.forEach { receiver -> receiver.dispatchDemoCommand(command, args) }
|
||||||
receiver.dispatchDemoCommand(command, args)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||||
@@ -183,65 +212,64 @@ class DemoModeController constructor(
|
|||||||
pw.println(" isDemoModeAllowed=$isAvailable")
|
pw.println(" isDemoModeAllowed=$isAvailable")
|
||||||
pw.print(" receivers=[")
|
pw.print(" receivers=[")
|
||||||
val copy: List<DemoModeCommandReceiver>
|
val copy: List<DemoModeCommandReceiver>
|
||||||
synchronized(this) {
|
synchronized(this) { copy = receivers.toList() }
|
||||||
copy = receivers.toList()
|
copy.forEach { recv -> pw.print(" ${recv.javaClass.simpleName}") }
|
||||||
}
|
|
||||||
copy.forEach { recv ->
|
|
||||||
pw.print(" ${recv.javaClass.simpleName}")
|
|
||||||
}
|
|
||||||
pw.println(" ]")
|
pw.println(" ]")
|
||||||
pw.println(" receiverMap= [")
|
pw.println(" receiverMap= [")
|
||||||
receiverMap.keys.forEach { command ->
|
receiverMap.keys.forEach { command ->
|
||||||
pw.print(" $command : [")
|
pw.print(" $command : [")
|
||||||
val recvs = receiverMap[command]!!.map { receiver ->
|
val recvs =
|
||||||
receiver.javaClass.simpleName
|
receiverMap[command]!!
|
||||||
}.joinToString(",")
|
.map { receiver -> receiver.javaClass.simpleName }
|
||||||
|
.joinToString(",")
|
||||||
pw.println("$recvs ]")
|
pw.println("$recvs ]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val tracker = object : DemoModeAvailabilityTracker(context) {
|
private val tracker =
|
||||||
override fun onDemoModeAvailabilityChanged() {
|
object : DemoModeAvailabilityTracker(context, globalSettings) {
|
||||||
setIsDemoModeAllowed(isDemoModeAvailable)
|
override fun onDemoModeAvailabilityChanged() {
|
||||||
}
|
setIsDemoModeAllowed(isDemoModeAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDemoModeStarted() {
|
override fun onDemoModeStarted() {
|
||||||
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
||||||
enterDemoMode()
|
enterDemoMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDemoModeFinished() {
|
||||||
|
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
||||||
|
exitDemoMode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDemoModeFinished() {
|
private val broadcastReceiver =
|
||||||
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
object : BroadcastReceiver() {
|
||||||
exitDemoMode()
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.v(TAG, "onReceive: $intent")
|
||||||
|
}
|
||||||
|
|
||||||
|
val action = intent.action
|
||||||
|
if (!ACTION_DEMO.equals(action)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val bundle = intent.extras ?: return
|
||||||
|
val command = bundle.getString("command", "").trim().lowercase()
|
||||||
|
if (command.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
dispatchDemoCommand(command, bundle)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.w(TAG, "Error running demo command, intent=$intent $t")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val broadcastReceiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.v(TAG, "onReceive: $intent")
|
|
||||||
}
|
|
||||||
|
|
||||||
val action = intent.action
|
|
||||||
if (!ACTION_DEMO.equals(action)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val bundle = intent.extras ?: return
|
|
||||||
val command = bundle.getString("command", "").trim().toLowerCase()
|
|
||||||
if (command.length == 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
dispatchDemoCommand(command, bundle)
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
Log.w(TAG, "Error running demo command, intent=$intent $t")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestSetDemoModeAllowed(allowed: Boolean) {
|
fun requestSetDemoModeAllowed(allowed: Boolean) {
|
||||||
setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
|
setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
|
||||||
@@ -258,10 +286,12 @@ class DemoModeController constructor(
|
|||||||
private fun setGlobal(key: String, value: Int) {
|
private fun setGlobal(key: String, value: Int) {
|
||||||
globalSettings.putInt(key, value)
|
globalSettings.putInt(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
|
||||||
|
const val DEMO_MODE_ON = "sysui_tuner_demo_on"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "DemoModeController"
|
private const val TAG = "DemoModeController"
|
||||||
private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
|
|
||||||
private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
|
|
||||||
|
|
||||||
private const val DEBUG = false
|
private const val DEBUG = false
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.android.systemui.demomode.dagger;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
import com.android.systemui.dagger.SysUISingleton;
|
import com.android.systemui.dagger.SysUISingleton;
|
||||||
import com.android.systemui.demomode.DemoModeController;
|
import com.android.systemui.demomode.DemoModeController;
|
||||||
import com.android.systemui.dump.DumpManager;
|
import com.android.systemui.dump.DumpManager;
|
||||||
@@ -37,8 +38,14 @@ public abstract class DemoModeModule {
|
|||||||
static DemoModeController provideDemoModeController(
|
static DemoModeController provideDemoModeController(
|
||||||
Context context,
|
Context context,
|
||||||
DumpManager dumpManager,
|
DumpManager dumpManager,
|
||||||
GlobalSettings globalSettings) {
|
GlobalSettings globalSettings,
|
||||||
DemoModeController dmc = new DemoModeController(context, dumpManager, globalSettings);
|
BroadcastDispatcher broadcastDispatcher
|
||||||
|
) {
|
||||||
|
DemoModeController dmc = new DemoModeController(
|
||||||
|
context,
|
||||||
|
dumpManager,
|
||||||
|
globalSettings,
|
||||||
|
broadcastDispatcher);
|
||||||
dmc.initialize();
|
dmc.initialize();
|
||||||
return /*run*/dmc;
|
return /*run*/dmc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1302,7 +1302,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String wifi = args.getString("wifi");
|
String wifi = args.getString("wifi");
|
||||||
if (wifi != null) {
|
if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
|
||||||
boolean show = wifi.equals("show");
|
boolean show = wifi.equals("show");
|
||||||
String level = args.getString("level");
|
String level = args.getString("level");
|
||||||
if (level != null) {
|
if (level != null) {
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat
|
|||||||
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
|
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
|
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -56,6 +58,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
|||||||
private final int mIconSize;
|
private final int mIconSize;
|
||||||
|
|
||||||
private StatusBarWifiView mWifiView;
|
private StatusBarWifiView mWifiView;
|
||||||
|
private ModernStatusBarWifiView mModernWifiView;
|
||||||
private boolean mDemoMode;
|
private boolean mDemoMode;
|
||||||
private int mColor;
|
private int mColor;
|
||||||
|
|
||||||
@@ -236,14 +239,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
|||||||
|
|
||||||
public void addDemoWifiView(WifiIconState state) {
|
public void addDemoWifiView(WifiIconState state) {
|
||||||
Log.d(TAG, "addDemoWifiView: ");
|
Log.d(TAG, "addDemoWifiView: ");
|
||||||
// TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}.
|
|
||||||
StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
|
StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
|
||||||
|
|
||||||
int viewIndex = getChildCount();
|
int viewIndex = getChildCount();
|
||||||
// If we have mobile views, put wifi before them
|
// If we have mobile views, put wifi before them
|
||||||
for (int i = 0; i < getChildCount(); i++) {
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
View child = getChildAt(i);
|
View child = getChildAt(i);
|
||||||
if (child instanceof StatusBarMobileView) {
|
if (child instanceof StatusBarMobileView
|
||||||
|
|| child instanceof ModernStatusBarMobileView) {
|
||||||
viewIndex = i;
|
viewIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -298,6 +301,30 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
|||||||
addView(view, getChildCount(), createLayoutParams());
|
addView(view, getChildCount(), createLayoutParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a {@link ModernStatusBarWifiView}
|
||||||
|
*/
|
||||||
|
public void addModernWifiView(LocationBasedWifiViewModel viewModel) {
|
||||||
|
Log.d(TAG, "addModernDemoWifiView: ");
|
||||||
|
ModernStatusBarWifiView view = ModernStatusBarWifiView
|
||||||
|
.constructAndBind(mContext, "wifi", viewModel);
|
||||||
|
|
||||||
|
int viewIndex = getChildCount();
|
||||||
|
// If we have mobile views, put wifi before them
|
||||||
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
|
View child = getChildAt(i);
|
||||||
|
if (child instanceof StatusBarMobileView
|
||||||
|
|| child instanceof ModernStatusBarMobileView) {
|
||||||
|
viewIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mModernWifiView = view;
|
||||||
|
mModernWifiView.setStaticDrawableColor(mColor);
|
||||||
|
addView(view, viewIndex, createLayoutParams());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply an update to a mobile icon view for the given {@link MobileIconState}. For
|
* Apply an update to a mobile icon view for the given {@link MobileIconState}. For
|
||||||
* compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
|
* compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
|
||||||
@@ -320,8 +347,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
|||||||
|
|
||||||
public void onRemoveIcon(StatusIconDisplayable view) {
|
public void onRemoveIcon(StatusIconDisplayable view) {
|
||||||
if (view.getSlot().equals("wifi")) {
|
if (view.getSlot().equals("wifi")) {
|
||||||
removeView(mWifiView);
|
if (view instanceof StatusBarWifiView) {
|
||||||
mWifiView = null;
|
removeView(mWifiView);
|
||||||
|
mWifiView = null;
|
||||||
|
} else if (view instanceof ModernStatusBarWifiView) {
|
||||||
|
Log.d(TAG, "onRemoveIcon: removing modern wifi view");
|
||||||
|
removeView(mModernWifiView);
|
||||||
|
mModernWifiView = null;
|
||||||
|
}
|
||||||
} else if (view instanceof StatusBarMobileView) {
|
} else if (view instanceof StatusBarMobileView) {
|
||||||
StatusBarMobileView mobileView = matchingMobileView(view);
|
StatusBarMobileView mobileView = matchingMobileView(view);
|
||||||
if (mobileView != null) {
|
if (mobileView != null) {
|
||||||
@@ -374,8 +407,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
|||||||
if (mWifiView != null) {
|
if (mWifiView != null) {
|
||||||
mWifiView.onDarkChanged(areas, darkIntensity, tint);
|
mWifiView.onDarkChanged(areas, darkIntensity, tint);
|
||||||
}
|
}
|
||||||
|
if (mModernWifiView != null) {
|
||||||
|
mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
|
||||||
|
}
|
||||||
for (StatusBarMobileView view : mMobileViews) {
|
for (StatusBarMobileView view : mMobileViews) {
|
||||||
view.onDarkChanged(areas, darkIntensity, tint);
|
view.onDarkChanged(areas, darkIntensity, tint);
|
||||||
}
|
}
|
||||||
|
for (ModernStatusBarMobileView view : mModernMobileViews) {
|
||||||
|
view.onDarkChanged(areas, darkIntensity, tint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -497,6 +497,11 @@ public interface StatusBarIconController {
|
|||||||
|
|
||||||
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
|
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
|
||||||
mGroup.addView(view, index, onCreateLayoutParams());
|
mGroup.addView(view, index, onCreateLayoutParams());
|
||||||
|
|
||||||
|
if (mIsInDemoMode) {
|
||||||
|
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,6 +693,9 @@ public interface StatusBarIconController {
|
|||||||
mIsInDemoMode = true;
|
mIsInDemoMode = true;
|
||||||
if (mDemoStatusIcons == null) {
|
if (mDemoStatusIcons == null) {
|
||||||
mDemoStatusIcons = createDemoStatusIcons();
|
mDemoStatusIcons = createDemoStatusIcons();
|
||||||
|
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
|
||||||
|
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mDemoStatusIcons.onDemoModeStarted();
|
mDemoStatusIcons.onDemoModeStarted();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyIm
|
|||||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
|
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
|
||||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
|
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
|
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
@@ -56,7 +56,7 @@ abstract class StatusBarPipelineModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
|
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
|
||||||
|
|
||||||
@Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
|
@Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
|
abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
|
|||||||
import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
|
import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
|
||||||
import com.android.settingslib.SignalIcon.MobileIconGroup
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
import com.android.settingslib.mobile.TelephonyIcons
|
import com.android.settingslib.mobile.TelephonyIcons
|
||||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.demomode.DemoMode
|
|
||||||
import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
|
import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
|
||||||
import com.android.systemui.demomode.DemoModeController
|
import com.android.systemui.demomode.DemoModeController
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
|
||||||
@@ -35,8 +33,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model
|
|||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
@@ -52,27 +48,7 @@ constructor(
|
|||||||
demoModeController: DemoModeController,
|
demoModeController: DemoModeController,
|
||||||
@Application scope: CoroutineScope,
|
@Application scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
private val demoCommandStream: Flow<Bundle> = conflatedCallbackFlow {
|
private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
|
||||||
val callback =
|
|
||||||
object : DemoMode {
|
|
||||||
override fun demoCommands(): List<String> = listOf(COMMAND_NETWORK)
|
|
||||||
|
|
||||||
override fun dispatchDemoCommand(command: String, args: Bundle) {
|
|
||||||
trySend(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDemoModeFinished() {
|
|
||||||
// Handled elsewhere
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDemoModeStarted() {
|
|
||||||
// Handled elsewhere
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
demoModeController.addCallback(callback)
|
|
||||||
awaitClose { demoModeController.removeCallback(callback) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
|
// If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
|
||||||
// commands work and it's a little silly
|
// commands work and it's a little silly
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.statusbar.pipeline.wifi.data.repository
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.demomode.DemoMode
|
||||||
|
import com.android.systemui.demomode.DemoModeController
|
||||||
|
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the [WifiRepository] interface either through the [DemoWifiRepository] implementation,
|
||||||
|
* or the [WifiRepositoryImpl]'s prod implementation, based on the current demo mode value. In this
|
||||||
|
* way, downstream clients can all consist of real implementations and not care about which
|
||||||
|
* repository is responsible for the data. Graphically:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* RealRepository
|
||||||
|
* │
|
||||||
|
* ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
|
||||||
|
* │
|
||||||
|
* DemoRepository
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* When demo mode turns on, every flow will [flatMapLatest] to the current provider's version of
|
||||||
|
* that flow.
|
||||||
|
*/
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class WifiRepositorySwitcher
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val realImpl: WifiRepositoryImpl,
|
||||||
|
private val demoImpl: DemoWifiRepository,
|
||||||
|
private val demoModeController: DemoModeController,
|
||||||
|
@Application scope: CoroutineScope,
|
||||||
|
) : WifiRepository {
|
||||||
|
private val isDemoMode =
|
||||||
|
conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : DemoMode {
|
||||||
|
override fun dispatchDemoCommand(command: String?, args: Bundle?) {
|
||||||
|
// Don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDemoModeStarted() {
|
||||||
|
demoImpl.startProcessingCommands()
|
||||||
|
trySend(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDemoModeFinished() {
|
||||||
|
demoImpl.stopProcessingCommands()
|
||||||
|
trySend(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
demoModeController.addCallback(callback)
|
||||||
|
awaitClose { demoModeController.removeCallback(callback) }
|
||||||
|
}
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
val activeRepo =
|
||||||
|
isDemoMode
|
||||||
|
.mapLatest { isDemoMode ->
|
||||||
|
if (isDemoMode) {
|
||||||
|
demoImpl
|
||||||
|
} else {
|
||||||
|
realImpl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
|
||||||
|
|
||||||
|
override val isWifiEnabled: StateFlow<Boolean> =
|
||||||
|
activeRepo
|
||||||
|
.flatMapLatest { it.isWifiEnabled }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiEnabled.value)
|
||||||
|
|
||||||
|
override val isWifiDefault: StateFlow<Boolean> =
|
||||||
|
activeRepo
|
||||||
|
.flatMapLatest { it.isWifiDefault }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiDefault.value)
|
||||||
|
|
||||||
|
override val wifiNetwork: StateFlow<WifiNetworkModel> =
|
||||||
|
activeRepo
|
||||||
|
.flatMapLatest { it.wifiNetwork }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
|
||||||
|
|
||||||
|
override val wifiActivity: StateFlow<DataActivityModel> =
|
||||||
|
activeRepo
|
||||||
|
.flatMapLatest { it.wifiActivity }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value)
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.statusbar.pipeline.wifi.data.repository.demo
|
||||||
|
|
||||||
|
import android.net.wifi.WifiManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
|
||||||
|
import com.android.systemui.demomode.DemoModeController
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.shareIn
|
||||||
|
|
||||||
|
/** Data source to map between demo mode commands and inputs into [DemoWifiRepository]'s flows */
|
||||||
|
@SysUISingleton
|
||||||
|
class DemoModeWifiDataSource
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
demoModeController: DemoModeController,
|
||||||
|
@Application scope: CoroutineScope,
|
||||||
|
) {
|
||||||
|
private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
|
||||||
|
private val _wifiCommands = demoCommandStream.map { args -> args.toWifiEvent() }
|
||||||
|
val wifiEvents = _wifiCommands.shareIn(scope, SharingStarted.WhileSubscribed())
|
||||||
|
|
||||||
|
private fun Bundle.toWifiEvent(): FakeWifiEventModel? {
|
||||||
|
val wifi = getString("wifi") ?: return null
|
||||||
|
return if (wifi == "show") {
|
||||||
|
activeWifiEvent()
|
||||||
|
} else {
|
||||||
|
FakeWifiEventModel.WifiDisabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Bundle.activeWifiEvent(): FakeWifiEventModel.Wifi {
|
||||||
|
val level = getString("level")?.toInt()
|
||||||
|
val activity = getString("activity")?.toActivity()
|
||||||
|
val ssid = getString("ssid")
|
||||||
|
val validated = getString("fully").toBoolean()
|
||||||
|
|
||||||
|
return FakeWifiEventModel.Wifi(
|
||||||
|
level = level,
|
||||||
|
activity = activity,
|
||||||
|
ssid = ssid,
|
||||||
|
validated = validated,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.toActivity(): Int =
|
||||||
|
when (this) {
|
||||||
|
"inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
|
||||||
|
"in" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN
|
||||||
|
"out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT
|
||||||
|
else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.statusbar.pipeline.wifi.data.repository.demo
|
||||||
|
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
|
||||||
|
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/** Demo-able wifi repository to support SystemUI demo mode commands. */
|
||||||
|
class DemoWifiRepository
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val dataSource: DemoModeWifiDataSource,
|
||||||
|
@Application private val scope: CoroutineScope,
|
||||||
|
) : WifiRepository {
|
||||||
|
private var demoCommandJob: Job? = null
|
||||||
|
|
||||||
|
private val _isWifiEnabled = MutableStateFlow(false)
|
||||||
|
override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
|
||||||
|
|
||||||
|
private val _isWifiDefault = MutableStateFlow(false)
|
||||||
|
override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
|
||||||
|
|
||||||
|
private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
|
||||||
|
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
|
||||||
|
|
||||||
|
private val _wifiActivity =
|
||||||
|
MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
|
||||||
|
override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
|
||||||
|
|
||||||
|
fun startProcessingCommands() {
|
||||||
|
demoCommandJob =
|
||||||
|
scope.launch {
|
||||||
|
dataSource.wifiEvents.filterNotNull().collect { event -> processEvent(event) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopProcessingCommands() {
|
||||||
|
demoCommandJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processEvent(event: FakeWifiEventModel) =
|
||||||
|
when (event) {
|
||||||
|
is FakeWifiEventModel.Wifi -> processEnabledWifiState(event)
|
||||||
|
is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processDisabledWifiState() {
|
||||||
|
_isWifiEnabled.value = false
|
||||||
|
_isWifiDefault.value = false
|
||||||
|
_wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
|
||||||
|
_wifiNetwork.value = WifiNetworkModel.Inactive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) {
|
||||||
|
_isWifiEnabled.value = true
|
||||||
|
_isWifiDefault.value = true
|
||||||
|
_wifiActivity.value =
|
||||||
|
event.activity?.toWifiDataActivityModel()
|
||||||
|
?: DataActivityModel(hasActivityIn = false, hasActivityOut = false)
|
||||||
|
_wifiNetwork.value = event.toWifiNetworkModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel =
|
||||||
|
WifiNetworkModel.Active(
|
||||||
|
networkId = DEMO_NET_ID,
|
||||||
|
isValidated = validated ?: true,
|
||||||
|
level = level,
|
||||||
|
ssid = ssid,
|
||||||
|
|
||||||
|
// These fields below aren't supported in demo mode, since they aren't needed to satisfy
|
||||||
|
// the interface.
|
||||||
|
isPasspointAccessPoint = false,
|
||||||
|
isOnlineSignUpForPasspointAccessPoint = false,
|
||||||
|
passpointProviderFriendlyName = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DEMO_NET_ID = 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.statusbar.pipeline.wifi.data.repository.demo.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for demo wifi commands, ported from [NetworkControllerImpl]
|
||||||
|
*
|
||||||
|
* Nullable fields represent optional command line arguments
|
||||||
|
*/
|
||||||
|
sealed interface FakeWifiEventModel {
|
||||||
|
data class Wifi(
|
||||||
|
val level: Int?,
|
||||||
|
val activity: Int?,
|
||||||
|
val ssid: String?,
|
||||||
|
val validated: Boolean?,
|
||||||
|
) : FakeWifiEventModel
|
||||||
|
|
||||||
|
object WifiDisabled : FakeWifiEventModel
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import com.android.systemui.R;
|
|||||||
import com.android.systemui.demomode.DemoMode;
|
import com.android.systemui.demomode.DemoMode;
|
||||||
import com.android.systemui.demomode.DemoModeAvailabilityTracker;
|
import com.android.systemui.demomode.DemoModeAvailabilityTracker;
|
||||||
import com.android.systemui.demomode.DemoModeController;
|
import com.android.systemui.demomode.DemoModeController;
|
||||||
|
import com.android.systemui.util.settings.GlobalSettings;
|
||||||
|
|
||||||
public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
|
public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
|
||||||
|
|
||||||
@@ -54,13 +55,15 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
|
|||||||
private SwitchPreference mOnSwitch;
|
private SwitchPreference mOnSwitch;
|
||||||
|
|
||||||
private DemoModeController mDemoModeController;
|
private DemoModeController mDemoModeController;
|
||||||
|
private GlobalSettings mGlobalSettings;
|
||||||
private Tracker mDemoModeTracker;
|
private Tracker mDemoModeTracker;
|
||||||
|
|
||||||
// We are the only ones who ever call this constructor, so don't worry about the warning
|
// We are the only ones who ever call this constructor, so don't worry about the warning
|
||||||
@SuppressLint("ValidFragment")
|
@SuppressLint("ValidFragment")
|
||||||
public DemoModeFragment(DemoModeController demoModeController) {
|
public DemoModeFragment(DemoModeController demoModeController, GlobalSettings globalSettings) {
|
||||||
super();
|
super();
|
||||||
mDemoModeController = demoModeController;
|
mDemoModeController = demoModeController;
|
||||||
|
mGlobalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
|
|||||||
screen.addPreference(mOnSwitch);
|
screen.addPreference(mOnSwitch);
|
||||||
setPreferenceScreen(screen);
|
setPreferenceScreen(screen);
|
||||||
|
|
||||||
mDemoModeTracker = new Tracker(context);
|
mDemoModeTracker = new Tracker(context, mGlobalSettings);
|
||||||
mDemoModeTracker.startTracking();
|
mDemoModeTracker.startTracking();
|
||||||
updateDemoModeEnabled();
|
updateDemoModeEnabled();
|
||||||
updateDemoModeOn();
|
updateDemoModeOn();
|
||||||
@@ -202,8 +205,8 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class Tracker extends DemoModeAvailabilityTracker {
|
private class Tracker extends DemoModeAvailabilityTracker {
|
||||||
Tracker(Context context) {
|
Tracker(Context context, GlobalSettings globalSettings) {
|
||||||
super(context);
|
super(context, globalSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.android.systemui.Dependency;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.demomode.DemoModeController;
|
import com.android.systemui.demomode.DemoModeController;
|
||||||
import com.android.systemui.fragments.FragmentService;
|
import com.android.systemui.fragments.FragmentService;
|
||||||
|
import com.android.systemui.util.settings.GlobalSettings;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -44,12 +45,18 @@ public class TunerActivity extends Activity implements
|
|||||||
|
|
||||||
private final DemoModeController mDemoModeController;
|
private final DemoModeController mDemoModeController;
|
||||||
private final TunerService mTunerService;
|
private final TunerService mTunerService;
|
||||||
|
private final GlobalSettings mGlobalSettings;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
TunerActivity(DemoModeController demoModeController, TunerService tunerService) {
|
TunerActivity(
|
||||||
|
DemoModeController demoModeController,
|
||||||
|
TunerService tunerService,
|
||||||
|
GlobalSettings globalSettings
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
mDemoModeController = demoModeController;
|
mDemoModeController = demoModeController;
|
||||||
mTunerService = tunerService;
|
mTunerService = tunerService;
|
||||||
|
mGlobalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -69,7 +76,7 @@ public class TunerActivity extends Activity implements
|
|||||||
boolean showDemoMode = action != null && action.equals(
|
boolean showDemoMode = action != null && action.equals(
|
||||||
"com.android.settings.action.DEMO_MODE");
|
"com.android.settings.action.DEMO_MODE");
|
||||||
final PreferenceFragment fragment = showDemoMode
|
final PreferenceFragment fragment = showDemoMode
|
||||||
? new DemoModeFragment(mDemoModeController)
|
? new DemoModeFragment(mDemoModeController, mGlobalSettings)
|
||||||
: new TunerFragment(mTunerService);
|
: new TunerFragment(mTunerService);
|
||||||
getFragmentManager().beginTransaction().replace(R.id.content_frame,
|
getFragmentManager().beginTransaction().replace(R.id.content_frame,
|
||||||
fragment, TAG_TUNER).commit();
|
fragment, TAG_TUNER).commit();
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.demomode
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.testing.TestableLooper
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.demomode.DemoMode.ACTION_DEMO
|
||||||
|
import com.android.systemui.demomode.DemoMode.COMMAND_STATUS
|
||||||
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.util.settings.FakeSettings
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
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
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@TestableLooper.RunWithLooper
|
||||||
|
@SmallTest
|
||||||
|
class DemoModeControllerTest : SysuiTestCase() {
|
||||||
|
private lateinit var underTest: DemoModeController
|
||||||
|
|
||||||
|
@Mock private lateinit var dumpManager: DumpManager
|
||||||
|
|
||||||
|
private val globalSettings = FakeSettings()
|
||||||
|
|
||||||
|
private val testDispatcher = UnconfinedTestDispatcher()
|
||||||
|
private val testScope = TestScope(testDispatcher)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
allowTestableLooperAsMainThread()
|
||||||
|
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
globalSettings.putInt(DemoModeController.DEMO_MODE_ALLOWED, 1)
|
||||||
|
globalSettings.putInt(DemoModeController.DEMO_MODE_ON, 1)
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
DemoModeController(
|
||||||
|
context = context,
|
||||||
|
dumpManager = dumpManager,
|
||||||
|
globalSettings = globalSettings,
|
||||||
|
broadcastDispatcher = fakeBroadcastDispatcher
|
||||||
|
)
|
||||||
|
|
||||||
|
underTest.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `demo command flow - returns args`() =
|
||||||
|
testScope.runTest {
|
||||||
|
var latest: Bundle? = null
|
||||||
|
val flow = underTest.demoFlowForCommand(TEST_COMMAND)
|
||||||
|
val job = launch { flow.collect { latest = it } }
|
||||||
|
|
||||||
|
sendDemoCommand(args = mapOf("key1" to "val1"))
|
||||||
|
assertThat(latest!!.getString("key1")).isEqualTo("val1")
|
||||||
|
|
||||||
|
sendDemoCommand(args = mapOf("key2" to "val2"))
|
||||||
|
assertThat(latest!!.getString("key2")).isEqualTo("val2")
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendDemoCommand(command: String? = TEST_COMMAND, args: Map<String, String>) {
|
||||||
|
val intent = Intent(ACTION_DEMO)
|
||||||
|
intent.putExtra("command", command)
|
||||||
|
args.forEach { arg -> intent.putExtra(arg.key, arg.value) }
|
||||||
|
|
||||||
|
fakeBroadcastDispatcher.registeredReceivers.forEach { it.onReceive(context, intent) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Use a valid command until we properly fake out everything
|
||||||
|
const val TEST_COMMAND = COMMAND_STATUS
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.statusbar.pipeline.wifi.data.repository
|
||||||
|
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.wifi.WifiManager
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.demomode.DemoMode
|
||||||
|
import com.android.systemui.demomode.DemoModeController
|
||||||
|
import com.android.systemui.log.table.TableLogBuffer
|
||||||
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
|
||||||
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
|
||||||
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
|
import com.android.systemui.util.mockito.kotlinArgumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@SmallTest
|
||||||
|
class WifiRepositorySwitcherTest : SysuiTestCase() {
|
||||||
|
private lateinit var underTest: WifiRepositorySwitcher
|
||||||
|
private lateinit var realImpl: WifiRepositoryImpl
|
||||||
|
private lateinit var demoImpl: DemoWifiRepository
|
||||||
|
|
||||||
|
@Mock private lateinit var demoModeController: DemoModeController
|
||||||
|
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||||
|
@Mock private lateinit var tableLogger: TableLogBuffer
|
||||||
|
@Mock private lateinit var connectivityManager: ConnectivityManager
|
||||||
|
@Mock private lateinit var wifiManager: WifiManager
|
||||||
|
@Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
|
||||||
|
private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
|
||||||
|
|
||||||
|
private val mainExecutor = FakeExecutor(FakeSystemClock())
|
||||||
|
|
||||||
|
private val testDispatcher = UnconfinedTestDispatcher()
|
||||||
|
private val testScope = TestScope(testDispatcher)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
// Never start in demo mode
|
||||||
|
whenever(demoModeController.isInDemoMode).thenReturn(false)
|
||||||
|
|
||||||
|
realImpl =
|
||||||
|
WifiRepositoryImpl(
|
||||||
|
fakeBroadcastDispatcher,
|
||||||
|
connectivityManager,
|
||||||
|
logger,
|
||||||
|
tableLogger,
|
||||||
|
mainExecutor,
|
||||||
|
testScope.backgroundScope,
|
||||||
|
wifiManager,
|
||||||
|
)
|
||||||
|
|
||||||
|
whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
|
||||||
|
|
||||||
|
demoImpl =
|
||||||
|
DemoWifiRepository(
|
||||||
|
demoModeWifiDataSource,
|
||||||
|
testScope.backgroundScope,
|
||||||
|
)
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
WifiRepositorySwitcher(
|
||||||
|
realImpl,
|
||||||
|
demoImpl,
|
||||||
|
demoModeController,
|
||||||
|
testScope.backgroundScope,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `switcher active repo - updates when demo mode changes`() =
|
||||||
|
testScope.runTest {
|
||||||
|
assertThat(underTest.activeRepo.value).isSameInstanceAs(realImpl)
|
||||||
|
|
||||||
|
var latest: WifiRepository? = null
|
||||||
|
val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
startDemoMode()
|
||||||
|
|
||||||
|
assertThat(latest).isSameInstanceAs(demoImpl)
|
||||||
|
|
||||||
|
finishDemoMode()
|
||||||
|
|
||||||
|
assertThat(latest).isSameInstanceAs(realImpl)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startDemoMode() {
|
||||||
|
whenever(demoModeController.isInDemoMode).thenReturn(true)
|
||||||
|
getDemoModeCallback().onDemoModeStarted()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishDemoMode() {
|
||||||
|
whenever(demoModeController.isInDemoMode).thenReturn(false)
|
||||||
|
getDemoModeCallback().onDemoModeFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDemoModeCallback(): DemoMode {
|
||||||
|
val captor = kotlinArgumentCaptor<DemoMode>()
|
||||||
|
Mockito.verify(demoModeController).addCallback(captor.capture())
|
||||||
|
return captor.value
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user