summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Laird <evanlaird@google.com> 2022-12-16 15:57:31 -0500
committer Evan Laird <evanlaird@google.com> 2022-12-21 15:19:21 -0500
commit2775b3b39e9eaee328130482688dd1f810b83e6a (patch)
tree78b115ad063126a909bf145bdecda407996fd9d2
parent81799a75f78c2df0ea29f457b737df4d80716325 (diff)
[Sb refactor] Add a demo command flow method
This CL mainly just adds a single method to DemoModeController, `demoFlowForCommand(String)`. The rest of the diff is from a combination of ktlint formatting and writing a test for the flow, which required some restructuring of the class. Test: DemoModeControllerTest Bug: 238425913 Change-Id: I40dd0b42a881d0776e9b19b282496e850a67a981
-rw-r--r--packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt186
-rw-r--r--packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt104
7 files changed, 247 insertions, 118 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
index 16f415070b45..c746efdbbd30 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
@@ -20,7 +20,7 @@ import android.content.Context
import android.database.ContentObserver
import android.os.Handler
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
@@ -29,7 +29,10 @@ import android.provider.Settings
* This class works by wrapping a content observer for the relevant keys related to DemoMode
* 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 isDemoModeAvailable = false
@@ -41,9 +44,9 @@ abstract class DemoModeAvailabilityTracker(val context: Context) {
fun startTracking() {
val resolver = context.contentResolver
resolver.registerContentObserver(
- Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
+ globalSettings.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
resolver.registerContentObserver(
- Settings.Global.getUriFor(DEMO_MODE_ON), false, onObserver)
+ globalSettings.getUriFor(DEMO_MODE_ON), false, onObserver)
}
fun stopTracking() {
@@ -57,12 +60,11 @@ abstract class DemoModeAvailabilityTracker(val context: Context) {
abstract fun onDemoModeFinished()
private fun checkIsDemoModeAllowed(): Boolean {
- return Settings.Global
- .getInt(context.contentResolver, DEMO_MODE_ALLOWED, 0) != 0
+ return globalSettings.getInt(DEMO_MODE_ALLOWED, 0) != 0
}
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())) {
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index 000bbe6afc50..84f83f1ae956 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -24,22 +24,28 @@ import android.os.Bundle
import android.os.UserHandle
import android.util.Log
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.dump.DumpManager
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.util.Assert
import com.android.systemui.util.settings.GlobalSettings
import java.io.PrintWriter
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
/**
* Handles system broadcasts for [DemoMode]
*
* Injected via [DemoModeModule]
*/
-class DemoModeController constructor(
+class DemoModeController
+constructor(
private val context: Context,
private val dumpManager: DumpManager,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
) : CallbackController<DemoMode>, Dumpable {
// Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
@@ -58,9 +64,7 @@ class DemoModeController constructor(
requestFinishDemoMode()
val m = mutableMapOf<String, MutableList<DemoMode>>()
- DemoMode.COMMANDS.map { command ->
- m.put(command, mutableListOf())
- }
+ DemoMode.COMMANDS.map { command -> m.put(command, mutableListOf()) }
receiverMap = m
}
@@ -71,7 +75,7 @@ class DemoModeController constructor(
initialized = true
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
// Due to DemoModeFragment running in systemui:tuner process, we have to observe for
// content changes to know if the setting turned on or off
@@ -81,8 +85,13 @@ class DemoModeController constructor(
val demoFilter = IntentFilter()
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) {
@@ -91,16 +100,15 @@ class DemoModeController constructor(
commands.forEach { command ->
if (!receiverMap.containsKey(command)) {
- throw IllegalStateException("Command ($command) not recognized. " +
- "See DemoMode.java for valid commands")
+ throw IllegalStateException(
+ "Command ($command) not recognized. " + "See DemoMode.java for valid commands"
+ )
}
receiverMap[command]!!.add(listener)
}
- synchronized(this) {
- receivers.add(listener)
- }
+ synchronized(this) { receivers.add(listener) }
if (isInDemoMode) {
listener.onDemoModeStarted()
@@ -109,14 +117,46 @@ class DemoModeController constructor(
override fun removeCallback(listener: DemoMode) {
synchronized(this) {
- listener.demoCommands().forEach { command ->
- receiverMap[command]!!.remove(listener)
- }
+ listener.demoCommands().forEach { command -> receiverMap[command]!!.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) {
// Turn off demo mode if it was on
if (isInDemoMode && !enabled) {
@@ -129,13 +169,9 @@ class DemoModeController constructor(
Assert.isMainThread()
val copy: List<DemoModeCommandReceiver>
- synchronized(this) {
- copy = receivers.toList()
- }
+ synchronized(this) { copy = receivers.toList() }
- copy.forEach { r ->
- r.onDemoModeStarted()
- }
+ copy.forEach { r -> r.onDemoModeStarted() }
}
private fun exitDemoMode() {
@@ -143,18 +179,13 @@ class DemoModeController constructor(
Assert.isMainThread()
val copy: List<DemoModeCommandReceiver>
- synchronized(this) {
- copy = receivers.toList()
- }
+ synchronized(this) { copy = receivers.toList() }
- copy.forEach { r ->
- r.onDemoModeFinished()
- }
+ copy.forEach { r -> r.onDemoModeFinished() }
}
fun dispatchDemoCommand(command: String, args: Bundle) {
Assert.isMainThread()
-
if (DEBUG) {
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
- receiverMap[command]!!.forEach { receiver ->
- receiver.dispatchDemoCommand(command, args)
- }
+ receiverMap[command]!!.forEach { receiver -> receiver.dispatchDemoCommand(command, args) }
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -183,65 +212,64 @@ class DemoModeController constructor(
pw.println(" isDemoModeAllowed=$isAvailable")
pw.print(" receivers=[")
val copy: List<DemoModeCommandReceiver>
- synchronized(this) {
- copy = receivers.toList()
- }
- copy.forEach { recv ->
- pw.print(" ${recv.javaClass.simpleName}")
- }
+ synchronized(this) { copy = receivers.toList() }
+ copy.forEach { recv -> pw.print(" ${recv.javaClass.simpleName}") }
pw.println(" ]")
pw.println(" receiverMap= [")
receiverMap.keys.forEach { command ->
pw.print(" $command : [")
- val recvs = receiverMap[command]!!.map { receiver ->
- receiver.javaClass.simpleName
- }.joinToString(",")
+ val recvs =
+ receiverMap[command]!!
+ .map { receiver -> receiver.javaClass.simpleName }
+ .joinToString(",")
pw.println("$recvs ]")
}
}
- private val tracker = object : DemoModeAvailabilityTracker(context) {
- override fun onDemoModeAvailabilityChanged() {
- setIsDemoModeAllowed(isDemoModeAvailable)
- }
-
- override fun onDemoModeStarted() {
- if (this@DemoModeController.isInDemoMode != isInDemoMode) {
- enterDemoMode()
+ private val tracker =
+ object : DemoModeAvailabilityTracker(context, globalSettings) {
+ override fun onDemoModeAvailabilityChanged() {
+ setIsDemoModeAllowed(isDemoModeAvailable)
}
- }
- override fun onDemoModeFinished() {
- if (this@DemoModeController.isInDemoMode != isInDemoMode) {
- exitDemoMode()
+ override fun onDemoModeStarted() {
+ if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+ enterDemoMode()
+ }
}
- }
- }
- 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
+ override fun onDemoModeFinished() {
+ if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+ exitDemoMode()
+ }
}
+ }
- 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().lowercase()
+ if (command.isEmpty()) {
+ return
+ }
+
+ try {
+ dispatchDemoCommand(command, bundle)
+ } catch (t: Throwable) {
+ Log.w(TAG, "Error running demo command, intent=$intent $t")
+ }
}
}
- }
fun requestSetDemoModeAllowed(allowed: Boolean) {
setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
@@ -258,10 +286,12 @@ class DemoModeController constructor(
private fun setGlobal(key: String, value: Int) {
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 DEMO_MODE_ALLOWED = "sysui_demo_allowed"
-private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
-
private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
index de9affb5c748..b84fa5af4d8d 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
@@ -18,6 +18,7 @@ package com.android.systemui.demomode.dagger;
import android.content.Context;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
@@ -37,8 +38,14 @@ public abstract class DemoModeModule {
static DemoModeController provideDemoModeController(
Context context,
DumpManager dumpManager,
- GlobalSettings globalSettings) {
- DemoModeController dmc = new DemoModeController(context, dumpManager, globalSettings);
+ GlobalSettings globalSettings,
+ BroadcastDispatcher broadcastDispatcher
+ ) {
+ DemoModeController dmc = new DemoModeController(
+ context,
+ dumpManager,
+ globalSettings,
+ broadcastDispatcher);
dmc.initialize();
return /*run*/dmc;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index a1ae8ed7329a..d4ddb856eecf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -24,10 +24,8 @@ import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
import com.android.settingslib.SignalIcon.MobileIconGroup
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.qualifiers.Application
-import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
import com.android.systemui.demomode.DemoModeController
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 javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
@@ -52,27 +48,7 @@ constructor(
demoModeController: DemoModeController,
@Application scope: CoroutineScope,
) {
- private val demoCommandStream: Flow<Bundle> = conflatedCallbackFlow {
- 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) }
- }
+ private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
// 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
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 1f444340653d..246488600eef 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -33,6 +33,7 @@ import com.android.systemui.R;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeAvailabilityTracker;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.util.settings.GlobalSettings;
public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
@@ -54,13 +55,15 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
private SwitchPreference mOnSwitch;
private DemoModeController mDemoModeController;
+ private GlobalSettings mGlobalSettings;
private Tracker mDemoModeTracker;
// We are the only ones who ever call this constructor, so don't worry about the warning
@SuppressLint("ValidFragment")
- public DemoModeFragment(DemoModeController demoModeController) {
+ public DemoModeFragment(DemoModeController demoModeController, GlobalSettings globalSettings) {
super();
mDemoModeController = demoModeController;
+ mGlobalSettings = globalSettings;
}
@@ -80,7 +83,7 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
screen.addPreference(mOnSwitch);
setPreferenceScreen(screen);
- mDemoModeTracker = new Tracker(context);
+ mDemoModeTracker = new Tracker(context, mGlobalSettings);
mDemoModeTracker.startTracking();
updateDemoModeEnabled();
updateDemoModeOn();
@@ -202,8 +205,8 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
}
private class Tracker extends DemoModeAvailabilityTracker {
- Tracker(Context context) {
- super(context);
+ Tracker(Context context, GlobalSettings globalSettings) {
+ super(context, globalSettings);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 3231aecdc4b2..32ecb6786a51 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -33,6 +33,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Inject;
@@ -44,12 +45,18 @@ public class TunerActivity extends Activity implements
private final DemoModeController mDemoModeController;
private final TunerService mTunerService;
+ private final GlobalSettings mGlobalSettings;
@Inject
- TunerActivity(DemoModeController demoModeController, TunerService tunerService) {
+ TunerActivity(
+ DemoModeController demoModeController,
+ TunerService tunerService,
+ GlobalSettings globalSettings
+ ) {
super();
mDemoModeController = demoModeController;
mTunerService = tunerService;
+ mGlobalSettings = globalSettings;
}
protected void onCreate(Bundle savedInstanceState) {
@@ -69,7 +76,7 @@ public class TunerActivity extends Activity implements
boolean showDemoMode = action != null && action.equals(
"com.android.settings.action.DEMO_MODE");
final PreferenceFragment fragment = showDemoMode
- ? new DemoModeFragment(mDemoModeController)
+ ? new DemoModeFragment(mDemoModeController, mGlobalSettings)
: new TunerFragment(mTunerService);
getFragmentManager().beginTransaction().replace(R.id.content_frame,
fragment, TAG_TUNER).commit();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
new file mode 100644
index 000000000000..87c66b5a98ac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -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
+ }
+}