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.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())) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
private val tracker =
|
||||
object : DemoModeAvailabilityTracker(context, globalSettings) {
|
||||
override fun onDemoModeAvailabilityChanged() {
|
||||
setIsDemoModeAllowed(isDemoModeAvailable)
|
||||
}
|
||||
|
||||
override fun onDemoModeStarted() {
|
||||
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
||||
enterDemoMode()
|
||||
override fun onDemoModeStarted() {
|
||||
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
||||
enterDemoMode()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDemoModeFinished() {
|
||||
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
||||
exitDemoMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDemoModeFinished() {
|
||||
if (this@DemoModeController.isInDemoMode != isInDemoMode) {
|
||||
exitDemoMode()
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1302,7 +1302,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
|
||||
}
|
||||
}
|
||||
String wifi = args.getString("wifi");
|
||||
if (wifi != null) {
|
||||
if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
|
||||
boolean show = wifi.equals("show");
|
||||
String level = args.getString("level");
|
||||
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.pipeline.mobile.ui.view.ModernStatusBarMobileView;
|
||||
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.List;
|
||||
@@ -56,6 +58,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
||||
private final int mIconSize;
|
||||
|
||||
private StatusBarWifiView mWifiView;
|
||||
private ModernStatusBarWifiView mModernWifiView;
|
||||
private boolean mDemoMode;
|
||||
private int mColor;
|
||||
|
||||
@@ -236,14 +239,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
||||
|
||||
public void addDemoWifiView(WifiIconState state) {
|
||||
Log.d(TAG, "addDemoWifiView: ");
|
||||
// TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}.
|
||||
StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
|
||||
|
||||
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) {
|
||||
if (child instanceof StatusBarMobileView
|
||||
|| child instanceof ModernStatusBarMobileView) {
|
||||
viewIndex = i;
|
||||
break;
|
||||
}
|
||||
@@ -298,6 +301,30 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
||||
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
|
||||
* 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) {
|
||||
if (view.getSlot().equals("wifi")) {
|
||||
removeView(mWifiView);
|
||||
mWifiView = null;
|
||||
if (view instanceof StatusBarWifiView) {
|
||||
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) {
|
||||
StatusBarMobileView mobileView = matchingMobileView(view);
|
||||
if (mobileView != null) {
|
||||
@@ -374,8 +407,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
|
||||
if (mWifiView != null) {
|
||||
mWifiView.onDarkChanged(areas, darkIntensity, tint);
|
||||
}
|
||||
if (mModernWifiView != null) {
|
||||
mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
|
||||
}
|
||||
for (StatusBarMobileView view : mMobileViews) {
|
||||
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);
|
||||
mGroup.addView(view, index, onCreateLayoutParams());
|
||||
|
||||
if (mIsInDemoMode) {
|
||||
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -688,6 +693,9 @@ public interface StatusBarIconController {
|
||||
mIsInDemoMode = true;
|
||||
if (mDemoStatusIcons == null) {
|
||||
mDemoStatusIcons = createDemoStatusIcons();
|
||||
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
|
||||
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
|
||||
}
|
||||
}
|
||||
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.ConnectivityRepositoryImpl
|
||||
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.WifiInteractorImpl
|
||||
import dagger.Binds
|
||||
@@ -56,7 +56,7 @@ abstract class StatusBarPipelineModule {
|
||||
@Binds
|
||||
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
|
||||
|
||||
@Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
|
||||
@Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
|
||||
|
||||
@Binds
|
||||
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 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
|
||||
|
||||
@@ -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.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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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