Add some fakes to SystemUI-test-utils
This CL adds fake implementations for: - FgsManagerController - UserTracker - SecurityController - UserInfoController It also adds a wrapper around a mocked UserSwitcherController to make it easier to mock in tests while still handling callbacks. Those fakes are going to be used to test the new implementation of the QSFooterActions, which are going to be refactored using the modern Android architecture. See ag/19678215 to see how they are used. Bug: 242040009 Test: atest FgsManagerControllerTest Test: atest FooterActionsViewModelTest Test: atest FooterActionsInteractorTest Change-Id: I0a3ef36de81c1bd48f33d4d234a4ee903bf40fc6
This commit is contained in:
@@ -68,9 +68,73 @@ import java.util.Objects
|
|||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
/** A controller for the dealing with services running in the foreground. */
|
||||||
|
interface FgsManagerController {
|
||||||
|
/** Whether the TaskManager (and therefore this controller) is actually available. */
|
||||||
|
val isAvailable: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/** The number of packages with a service running in the foreground. */
|
||||||
|
val numRunningPackages: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether there were new changes to the foreground services since the last [shown][showDialog]
|
||||||
|
* dialog was dismissed.
|
||||||
|
*/
|
||||||
|
val newChangesSinceDialogWasDismissed: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we should show a dot to indicate when [newChangesSinceDialogWasDismissed] is true.
|
||||||
|
*/
|
||||||
|
val showFooterDot: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize this controller. This should be called once, before this controller is used for
|
||||||
|
* the first time.
|
||||||
|
*/
|
||||||
|
fun init()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the foreground services dialog. The dialog will be expanded from [viewLaunchedFrom] if
|
||||||
|
* it's not `null`.
|
||||||
|
*/
|
||||||
|
fun showDialog(viewLaunchedFrom: View?)
|
||||||
|
|
||||||
|
/** Add a [OnNumberOfPackagesChangedListener]. */
|
||||||
|
fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
|
||||||
|
|
||||||
|
/** Remove a [OnNumberOfPackagesChangedListener]. */
|
||||||
|
fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
|
||||||
|
|
||||||
|
/** Add a [OnDialogDismissedListener]. */
|
||||||
|
fun addOnDialogDismissedListener(listener: OnDialogDismissedListener)
|
||||||
|
|
||||||
|
/** Remove a [OnDialogDismissedListener]. */
|
||||||
|
fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener)
|
||||||
|
|
||||||
|
/** Whether we should update the footer visibility. */
|
||||||
|
// TODO(b/242040009): Remove this.
|
||||||
|
fun shouldUpdateFooterVisibility(): Boolean
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
fun visibleButtonsCount(): Int
|
||||||
|
|
||||||
|
interface OnNumberOfPackagesChangedListener {
|
||||||
|
/** Called when [numRunningPackages] changed. */
|
||||||
|
fun onNumberOfPackagesChanged(numPackages: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnDialogDismissedListener {
|
||||||
|
/** Called when a dialog shown using [showDialog] was dismissed. */
|
||||||
|
fun onDialogDismissed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class FgsManagerController @Inject constructor(
|
class FgsManagerControllerImpl @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@Main private val mainExecutor: Executor,
|
@Main private val mainExecutor: Executor,
|
||||||
@Background private val backgroundExecutor: Executor,
|
@Background private val backgroundExecutor: Executor,
|
||||||
@@ -82,25 +146,32 @@ class FgsManagerController @Inject constructor(
|
|||||||
private val dialogLaunchAnimator: DialogLaunchAnimator,
|
private val dialogLaunchAnimator: DialogLaunchAnimator,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
private val dumpManager: DumpManager
|
private val dumpManager: DumpManager
|
||||||
) : IForegroundServiceObserver.Stub(), Dumpable {
|
) : IForegroundServiceObserver.Stub(), Dumpable, FgsManagerController {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val INTERACTION_JANK_TAG = "active_background_apps"
|
private const val INTERACTION_JANK_TAG = "active_background_apps"
|
||||||
private val LOG_TAG = FgsManagerController::class.java.simpleName
|
|
||||||
private const val DEFAULT_TASK_MANAGER_ENABLED = true
|
private const val DEFAULT_TASK_MANAGER_ENABLED = true
|
||||||
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
|
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
|
||||||
private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
|
private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var changesSinceDialog = false
|
override var newChangesSinceDialogWasDismissed = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var isAvailable = false
|
val _isAvailable = MutableStateFlow(false)
|
||||||
private set
|
override val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()
|
||||||
var showFooterDot = false
|
|
||||||
private set
|
val _showFooterDot = MutableStateFlow(false)
|
||||||
var showStopBtnForUserAllowlistedApps = false
|
override val showFooterDot: StateFlow<Boolean> = _showFooterDot.asStateFlow()
|
||||||
private set
|
|
||||||
|
private var showStopBtnForUserAllowlistedApps = false
|
||||||
|
|
||||||
|
override val numRunningPackages: Int
|
||||||
|
get() {
|
||||||
|
synchronized(lock) {
|
||||||
|
return getNumVisiblePackagesLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val lock = Any()
|
private val lock = Any()
|
||||||
|
|
||||||
@@ -138,15 +209,7 @@ class FgsManagerController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnNumberOfPackagesChangedListener {
|
override fun init() {
|
||||||
fun onNumberOfPackagesChanged(numPackages: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OnDialogDismissedListener {
|
|
||||||
fun onDialogDismissed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun init() {
|
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
return
|
return
|
||||||
@@ -165,19 +228,19 @@ class FgsManagerController @Inject constructor(
|
|||||||
NAMESPACE_SYSTEMUI,
|
NAMESPACE_SYSTEMUI,
|
||||||
backgroundExecutor
|
backgroundExecutor
|
||||||
) {
|
) {
|
||||||
isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
|
_isAvailable.value = it.getBoolean(TASK_MANAGER_ENABLED, _isAvailable.value)
|
||||||
showFooterDot =
|
_showFooterDot.value =
|
||||||
it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot)
|
it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value)
|
||||||
showStopBtnForUserAllowlistedApps = it.getBoolean(
|
showStopBtnForUserAllowlistedApps = it.getBoolean(
|
||||||
TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
|
TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
|
||||||
showStopBtnForUserAllowlistedApps)
|
showStopBtnForUserAllowlistedApps)
|
||||||
}
|
}
|
||||||
|
|
||||||
isAvailable = deviceConfigProxy.getBoolean(
|
_isAvailable.value = deviceConfigProxy.getBoolean(
|
||||||
NAMESPACE_SYSTEMUI,
|
NAMESPACE_SYSTEMUI,
|
||||||
TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED
|
TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED
|
||||||
)
|
)
|
||||||
showFooterDot = deviceConfigProxy.getBoolean(
|
_showFooterDot.value = deviceConfigProxy.getBoolean(
|
||||||
NAMESPACE_SYSTEMUI,
|
NAMESPACE_SYSTEMUI,
|
||||||
TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
|
TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
|
||||||
)
|
)
|
||||||
@@ -232,42 +295,45 @@ class FgsManagerController @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
val onNumberOfPackagesChangedListeners: MutableSet<OnNumberOfPackagesChangedListener> =
|
private val onNumberOfPackagesChangedListeners =
|
||||||
mutableSetOf()
|
mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>()
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
val onDialogDismissedListeners: MutableSet<OnDialogDismissedListener> = mutableSetOf()
|
private val onDialogDismissedListeners =
|
||||||
|
mutableSetOf<FgsManagerController.OnDialogDismissedListener>()
|
||||||
|
|
||||||
fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
|
override fun addOnNumberOfPackagesChangedListener(
|
||||||
|
listener: FgsManagerController.OnNumberOfPackagesChangedListener
|
||||||
|
) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
onNumberOfPackagesChangedListeners.add(listener)
|
onNumberOfPackagesChangedListeners.add(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
|
override fun removeOnNumberOfPackagesChangedListener(
|
||||||
|
listener: FgsManagerController.OnNumberOfPackagesChangedListener
|
||||||
|
) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
onNumberOfPackagesChangedListeners.remove(listener)
|
onNumberOfPackagesChangedListeners.remove(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) {
|
override fun addOnDialogDismissedListener(
|
||||||
|
listener: FgsManagerController.OnDialogDismissedListener
|
||||||
|
) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
onDialogDismissedListeners.add(listener)
|
onDialogDismissedListeners.add(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) {
|
override fun removeOnDialogDismissedListener(
|
||||||
|
listener: FgsManagerController.OnDialogDismissedListener
|
||||||
|
) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
onDialogDismissedListeners.remove(listener)
|
onDialogDismissedListeners.remove(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNumRunningPackages(): Int {
|
|
||||||
synchronized(lock) {
|
|
||||||
return getNumVisiblePackagesLocked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNumVisiblePackagesLocked(): Int {
|
private fun getNumVisiblePackagesLocked(): Int {
|
||||||
return runningServiceTokens.keys.count {
|
return runningServiceTokens.keys.count {
|
||||||
it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
|
it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
|
||||||
@@ -278,7 +344,7 @@ class FgsManagerController @Inject constructor(
|
|||||||
val num = getNumVisiblePackagesLocked()
|
val num = getNumVisiblePackagesLocked()
|
||||||
if (num != lastNumberOfVisiblePackages) {
|
if (num != lastNumberOfVisiblePackages) {
|
||||||
lastNumberOfVisiblePackages = num
|
lastNumberOfVisiblePackages = num
|
||||||
changesSinceDialog = true
|
newChangesSinceDialogWasDismissed = true
|
||||||
onNumberOfPackagesChangedListeners.forEach {
|
onNumberOfPackagesChangedListeners.forEach {
|
||||||
backgroundExecutor.execute {
|
backgroundExecutor.execute {
|
||||||
it.onNumberOfPackagesChanged(num)
|
it.onNumberOfPackagesChanged(num)
|
||||||
@@ -287,9 +353,7 @@ class FgsManagerController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
override fun visibleButtonsCount(): Int {
|
||||||
@JvmName("getNumVisibleButtons")
|
|
||||||
internal fun getNumVisibleButtons(): Int {
|
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
return getNumVisibleButtonsLocked()
|
return getNumVisibleButtonsLocked()
|
||||||
}
|
}
|
||||||
@@ -301,9 +365,9 @@ class FgsManagerController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldUpdateFooterVisibility() = dialog == null
|
override fun shouldUpdateFooterVisibility() = dialog == null
|
||||||
|
|
||||||
fun showDialog(viewLaunchedFrom: View?) {
|
override fun showDialog(viewLaunchedFrom: View?) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
if (dialog == null) {
|
if (dialog == null) {
|
||||||
|
|
||||||
@@ -328,7 +392,7 @@ class FgsManagerController @Inject constructor(
|
|||||||
this.dialog = dialog
|
this.dialog = dialog
|
||||||
|
|
||||||
dialog.setOnDismissListener {
|
dialog.setOnDismissListener {
|
||||||
changesSinceDialog = false
|
newChangesSinceDialogWasDismissed = false
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
this.dialog = null
|
this.dialog = null
|
||||||
updateAppItemsLocked()
|
updateAppItemsLocked()
|
||||||
@@ -656,7 +720,7 @@ class FgsManagerController @Inject constructor(
|
|||||||
val pw = IndentingPrintWriter(printwriter)
|
val pw = IndentingPrintWriter(printwriter)
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
pw.println("current user profiles = $currentProfileIds")
|
pw.println("current user profiles = $currentProfileIds")
|
||||||
pw.println("changesSinceDialog=$changesSinceDialog")
|
pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed")
|
||||||
pw.println("Running service tokens: [")
|
pw.println("Running service tokens: [")
|
||||||
pw.indentIfPossible {
|
pw.indentIfPossible {
|
||||||
runningServiceTokens.forEach { (userPackage, startTimeAndTokens) ->
|
runningServiceTokens.forEach { (userPackage, startTimeAndTokens) ->
|
||||||
|
|||||||
@@ -149,9 +149,11 @@ public class QSFgsManagerFooter implements View.OnClickListener,
|
|||||||
mNumberView.setContentDescription(text);
|
mNumberView.setContentDescription(text);
|
||||||
if (mFgsManagerController.shouldUpdateFooterVisibility()) {
|
if (mFgsManagerController.shouldUpdateFooterVisibility()) {
|
||||||
mRootView.setVisibility(mNumPackages > 0
|
mRootView.setVisibility(mNumPackages > 0
|
||||||
&& mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE);
|
&& mFgsManagerController.isAvailable().getValue() ? View.VISIBLE
|
||||||
int dotVis = mFgsManagerController.getShowFooterDot()
|
: View.GONE);
|
||||||
&& mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE;
|
int dotVis = mFgsManagerController.getShowFooterDot().getValue()
|
||||||
|
&& mFgsManagerController.getNewChangesSinceDialogWasDismissed()
|
||||||
|
? View.VISIBLE : View.GONE;
|
||||||
mDotView.setVisibility(dotVis);
|
mDotView.setVisibility(dotVis);
|
||||||
mCollapsedDotView.setVisibility(dotVis);
|
mCollapsedDotView.setVisibility(dotVis);
|
||||||
if (mVisibilityChangedListener != null) {
|
if (mVisibilityChangedListener != null) {
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import com.android.systemui.battery.BatteryMeterView;
|
|||||||
import com.android.systemui.dagger.qualifiers.RootView;
|
import com.android.systemui.dagger.qualifiers.RootView;
|
||||||
import com.android.systemui.plugins.qs.QS;
|
import com.android.systemui.plugins.qs.QS;
|
||||||
import com.android.systemui.privacy.OngoingPrivacyChip;
|
import com.android.systemui.privacy.OngoingPrivacyChip;
|
||||||
|
import com.android.systemui.qs.FgsManagerController;
|
||||||
|
import com.android.systemui.qs.FgsManagerControllerImpl;
|
||||||
import com.android.systemui.qs.FooterActionsView;
|
import com.android.systemui.qs.FooterActionsView;
|
||||||
import com.android.systemui.qs.QSContainerImpl;
|
import com.android.systemui.qs.QSContainerImpl;
|
||||||
import com.android.systemui.qs.QSFooter;
|
import com.android.systemui.qs.QSFooter;
|
||||||
@@ -194,4 +196,8 @@ public interface QSFragmentModule {
|
|||||||
) {
|
) {
|
||||||
return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
|
return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
@Binds
|
||||||
|
FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
|
||||||
}
|
}
|
||||||
@@ -188,9 +188,9 @@ public class FgsManagerControllerTest extends SysuiTestCase {
|
|||||||
public void testChangesSinceLastDialog() throws RemoteException {
|
public void testChangesSinceLastDialog() throws RemoteException {
|
||||||
setUserProfiles(0);
|
setUserProfiles(0);
|
||||||
|
|
||||||
Assert.assertFalse(mFmc.getChangesSinceDialog());
|
Assert.assertFalse(mFmc.getNewChangesSinceDialogWasDismissed());
|
||||||
mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg", 0, true);
|
mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg", 0, true);
|
||||||
Assert.assertTrue(mFmc.getChangesSinceDialog());
|
Assert.assertTrue(mFmc.getNewChangesSinceDialogWasDismissed());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -233,14 +233,14 @@ public class FgsManagerControllerTest extends SysuiTestCase {
|
|||||||
final Binder binder = new Binder();
|
final Binder binder = new Binder();
|
||||||
setShowStopButtonForUserAllowlistedApps(true);
|
setShowStopButtonForUserAllowlistedApps(true);
|
||||||
mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
|
mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
|
||||||
Assert.assertEquals(1, mFmc.getNumVisibleButtons());
|
Assert.assertEquals(1, mFmc.visibleButtonsCount());
|
||||||
|
|
||||||
mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, false);
|
mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, false);
|
||||||
Assert.assertEquals(0, mFmc.getNumVisibleButtons());
|
Assert.assertEquals(0, mFmc.visibleButtonsCount());
|
||||||
|
|
||||||
setShowStopButtonForUserAllowlistedApps(false);
|
setShowStopButtonForUserAllowlistedApps(false);
|
||||||
mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
|
mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
|
||||||
Assert.assertEquals(0, mFmc.getNumVisibleButtons());
|
Assert.assertEquals(0, mFmc.visibleButtonsCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
|
private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
|
||||||
@@ -269,7 +269,7 @@ public class FgsManagerControllerTest extends SysuiTestCase {
|
|||||||
ArgumentCaptor<BroadcastReceiver> showFgsManagerReceiverArgumentCaptor =
|
ArgumentCaptor<BroadcastReceiver> showFgsManagerReceiverArgumentCaptor =
|
||||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||||
|
|
||||||
FgsManagerController result = new FgsManagerController(
|
FgsManagerController result = new FgsManagerControllerImpl(
|
||||||
mContext,
|
mContext,
|
||||||
mMainExecutor,
|
mMainExecutor,
|
||||||
mBackgroundExecutor,
|
mBackgroundExecutor,
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ class FakeFeatureFlags : FeatureFlags {
|
|||||||
stringFlags.put(flag.id, value)
|
stringFlags.put(flag.id, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id)
|
override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id)
|
||||||
|
|
||||||
override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.id)
|
override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.id)
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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.qs
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import com.android.systemui.qs.FgsManagerController.OnDialogDismissedListener
|
||||||
|
import com.android.systemui.qs.FgsManagerController.OnNumberOfPackagesChangedListener
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
/** A fake [FgsManagerController] to be used in tests. */
|
||||||
|
class FakeFgsManagerController(
|
||||||
|
isAvailable: Boolean = true,
|
||||||
|
showFooterDot: Boolean = false,
|
||||||
|
numRunningPackages: Int = 0,
|
||||||
|
) : FgsManagerController {
|
||||||
|
override val isAvailable: MutableStateFlow<Boolean> = MutableStateFlow(isAvailable)
|
||||||
|
|
||||||
|
override var numRunningPackages = numRunningPackages
|
||||||
|
set(value) {
|
||||||
|
if (value != field) {
|
||||||
|
field = value
|
||||||
|
newChangesSinceDialogWasDismissed = true
|
||||||
|
numRunningPackagesListeners.forEach { it.onNumberOfPackagesChanged(value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var newChangesSinceDialogWasDismissed = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override val showFooterDot: MutableStateFlow<Boolean> = MutableStateFlow(showFooterDot)
|
||||||
|
|
||||||
|
private val numRunningPackagesListeners = LinkedHashSet<OnNumberOfPackagesChangedListener>()
|
||||||
|
private val dialogDismissedListeners = LinkedHashSet<OnDialogDismissedListener>()
|
||||||
|
|
||||||
|
/** Simulate that a fgs dialog was just dismissed. */
|
||||||
|
fun simulateDialogDismiss() {
|
||||||
|
newChangesSinceDialogWasDismissed = false
|
||||||
|
dialogDismissedListeners.forEach { it.onDialogDismissed() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun init() {}
|
||||||
|
|
||||||
|
override fun showDialog(viewLaunchedFrom: View?) {}
|
||||||
|
|
||||||
|
override fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
|
||||||
|
numRunningPackagesListeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeOnNumberOfPackagesChangedListener(
|
||||||
|
listener: OnNumberOfPackagesChangedListener
|
||||||
|
) {
|
||||||
|
numRunningPackagesListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) {
|
||||||
|
dialogDismissedListeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) {
|
||||||
|
dialogDismissedListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldUpdateFooterVisibility(): Boolean = false
|
||||||
|
|
||||||
|
override fun visibleButtonsCount(): Int = 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.UserInfo
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.test.mock.MockContentResolver
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
/** A fake [UserTracker] to be used in tests. */
|
||||||
|
class FakeUserTracker(
|
||||||
|
userId: Int = 0,
|
||||||
|
userHandle: UserHandle = UserHandle.of(userId),
|
||||||
|
userInfo: UserInfo = mock(),
|
||||||
|
userProfiles: List<UserInfo> = emptyList(),
|
||||||
|
userContentResolver: ContentResolver = MockContentResolver(),
|
||||||
|
userContext: Context = mock(),
|
||||||
|
private val onCreateCurrentUserContext: (Context) -> Context = { mock() },
|
||||||
|
) : UserTracker {
|
||||||
|
val callbacks = mutableListOf<UserTracker.Callback>()
|
||||||
|
|
||||||
|
override val userId: Int = userId
|
||||||
|
override val userHandle: UserHandle = userHandle
|
||||||
|
override val userInfo: UserInfo = userInfo
|
||||||
|
override val userProfiles: List<UserInfo> = userProfiles
|
||||||
|
|
||||||
|
override val userContentResolver: ContentResolver = userContentResolver
|
||||||
|
override val userContext: Context = userContext
|
||||||
|
|
||||||
|
override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
|
||||||
|
callbacks.add(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeCallback(callback: UserTracker.Callback) {
|
||||||
|
callbacks.remove(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCurrentUserContext(context: Context): Context {
|
||||||
|
return onCreateCurrentUserContext(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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.policy
|
||||||
|
|
||||||
|
import android.app.admin.DeviceAdminInfo
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import java.io.PrintWriter
|
||||||
|
|
||||||
|
/** A fake [SecurityController] to be used in tests. */
|
||||||
|
class FakeSecurityController(
|
||||||
|
private val fakeState: FakeState = FakeState(),
|
||||||
|
) : SecurityController {
|
||||||
|
private val callbacks = LinkedHashSet<SecurityController.SecurityControllerCallback>()
|
||||||
|
|
||||||
|
override fun addCallback(callback: SecurityController.SecurityControllerCallback) {
|
||||||
|
callbacks.add(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeCallback(callback: SecurityController.SecurityControllerCallback) {
|
||||||
|
callbacks.remove(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update [fakeState], then notify the callbacks. */
|
||||||
|
fun updateState(f: FakeState.() -> Unit) {
|
||||||
|
fakeState.f()
|
||||||
|
callbacks.forEach { it.onStateChanged() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dump(pw: PrintWriter, args: Array<out String>) {}
|
||||||
|
|
||||||
|
override fun isDeviceManaged(): Boolean = fakeState.isDeviceManaged
|
||||||
|
|
||||||
|
override fun hasProfileOwner(): Boolean = fakeState.hasProfileOwner
|
||||||
|
|
||||||
|
override fun hasWorkProfile(): Boolean = fakeState.hasWorkProfile
|
||||||
|
|
||||||
|
override fun isWorkProfileOn(): Boolean = fakeState.isWorkProfileOn
|
||||||
|
|
||||||
|
override fun isProfileOwnerOfOrganizationOwnedDevice(): Boolean =
|
||||||
|
fakeState.isProfileOwnerOfOrganizationOwnedDevice
|
||||||
|
|
||||||
|
override fun getDeviceOwnerName(): String? = fakeState.deviceOwnerName
|
||||||
|
|
||||||
|
override fun getProfileOwnerName(): String? = fakeState.profileOwnerName
|
||||||
|
|
||||||
|
override fun getDeviceOwnerOrganizationName(): String? = fakeState.deviceOwnerOrganizationName
|
||||||
|
|
||||||
|
override fun getWorkProfileOrganizationName(): String? = fakeState.workProfileOrganizationName
|
||||||
|
|
||||||
|
override fun getDeviceOwnerComponentOnAnyUser(): ComponentName? =
|
||||||
|
fakeState.deviceOwnerComponentOnAnyUser
|
||||||
|
|
||||||
|
override fun getDeviceOwnerType(admin: ComponentName?): Int = 0
|
||||||
|
|
||||||
|
override fun isNetworkLoggingEnabled(): Boolean = fakeState.isNetworkLoggingEnabled
|
||||||
|
|
||||||
|
override fun isVpnEnabled(): Boolean = fakeState.isVpnEnabled
|
||||||
|
|
||||||
|
override fun isVpnRestricted(): Boolean = fakeState.isVpnRestricted
|
||||||
|
|
||||||
|
override fun isVpnBranded(): Boolean = fakeState.isVpnBranded
|
||||||
|
|
||||||
|
override fun getPrimaryVpnName(): String? = fakeState.primaryVpnName
|
||||||
|
|
||||||
|
override fun getWorkProfileVpnName(): String? = fakeState.workProfileVpnName
|
||||||
|
|
||||||
|
override fun hasCACertInCurrentUser(): Boolean = fakeState.hasCACertInCurrentUser
|
||||||
|
|
||||||
|
override fun hasCACertInWorkProfile(): Boolean = fakeState.hasCACertInWorkProfile
|
||||||
|
|
||||||
|
override fun onUserSwitched(newUserId: Int) {}
|
||||||
|
|
||||||
|
override fun isParentalControlsEnabled(): Boolean = fakeState.isParentalControlsEnabled
|
||||||
|
|
||||||
|
override fun getDeviceAdminInfo(): DeviceAdminInfo? = fakeState.deviceAdminInfo
|
||||||
|
|
||||||
|
override fun getIcon(info: DeviceAdminInfo?): Drawable? = null
|
||||||
|
|
||||||
|
override fun getLabel(info: DeviceAdminInfo?): CharSequence? = null
|
||||||
|
|
||||||
|
class FakeState(
|
||||||
|
var isDeviceManaged: Boolean = false,
|
||||||
|
var hasProfileOwner: Boolean = false,
|
||||||
|
var hasWorkProfile: Boolean = false,
|
||||||
|
var isWorkProfileOn: Boolean = false,
|
||||||
|
var isProfileOwnerOfOrganizationOwnedDevice: Boolean = false,
|
||||||
|
var deviceOwnerName: String? = null,
|
||||||
|
var profileOwnerName: String? = null,
|
||||||
|
var deviceOwnerOrganizationName: String? = null,
|
||||||
|
var workProfileOrganizationName: String? = null,
|
||||||
|
var deviceOwnerComponentOnAnyUser: ComponentName? = null,
|
||||||
|
var isNetworkLoggingEnabled: Boolean = false,
|
||||||
|
var isVpnEnabled: Boolean = false,
|
||||||
|
var isVpnRestricted: Boolean = false,
|
||||||
|
var isVpnBranded: Boolean = false,
|
||||||
|
var primaryVpnName: String? = null,
|
||||||
|
var workProfileVpnName: String? = null,
|
||||||
|
var hasCACertInCurrentUser: Boolean = false,
|
||||||
|
var hasCACertInWorkProfile: Boolean = false,
|
||||||
|
var isParentalControlsEnabled: Boolean = false,
|
||||||
|
var deviceAdminInfo: DeviceAdminInfo? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.policy
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
|
||||||
|
/** A fake [UserInfoController] to be used in tests. */
|
||||||
|
class FakeUserInfoController(
|
||||||
|
private val fakeInfo: FakeInfo = FakeInfo(),
|
||||||
|
) : UserInfoController {
|
||||||
|
private val listeners = LinkedHashSet<UserInfoController.OnUserInfoChangedListener>()
|
||||||
|
|
||||||
|
/** Update [fakeInfo], then notify the listeners. */
|
||||||
|
fun updateInfo(f: FakeInfo.() -> Unit) {
|
||||||
|
fakeInfo.f()
|
||||||
|
notifyListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyListeners() {
|
||||||
|
listeners.forEach { listener ->
|
||||||
|
listener.onUserInfoChanged(fakeInfo.name, fakeInfo.picture, fakeInfo.userAccount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addCallback(listener: UserInfoController.OnUserInfoChangedListener) {
|
||||||
|
listeners.add(listener)
|
||||||
|
|
||||||
|
// The actual implementation notifies the listener when adding it.
|
||||||
|
listener.onUserInfoChanged(fakeInfo.name, fakeInfo.picture, fakeInfo.userAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeCallback(listener: UserInfoController.OnUserInfoChangedListener) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reloadUserInfo() {}
|
||||||
|
|
||||||
|
class FakeInfo(
|
||||||
|
var name: String = "",
|
||||||
|
var picture: Drawable = mock(),
|
||||||
|
var userAccount: String = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.policy
|
||||||
|
|
||||||
|
import com.android.systemui.statusbar.policy.UserSwitcherController.UserSwitchCallback
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around a mocked [UserSwitcherController] to be used in tests.
|
||||||
|
*
|
||||||
|
* Note that this was implemented as a mock wrapper instead of fake implementation of a common
|
||||||
|
* interface given how big the UserSwitcherController grew.
|
||||||
|
*/
|
||||||
|
class MockUserSwitcherControllerWrapper(
|
||||||
|
currentUserName: String = "",
|
||||||
|
) {
|
||||||
|
val controller: UserSwitcherController = mock()
|
||||||
|
private val callbacks = LinkedHashSet<UserSwitchCallback>()
|
||||||
|
|
||||||
|
var currentUserName = currentUserName
|
||||||
|
set(value) {
|
||||||
|
if (value != field) {
|
||||||
|
field = value
|
||||||
|
notifyCallbacks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyCallbacks() {
|
||||||
|
callbacks.forEach { it.onUserSwitched() }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
whenever(controller.addUserSwitchCallback(any())).then { invocation ->
|
||||||
|
callbacks.add(invocation.arguments.first() as UserSwitchCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
whenever(controller.removeUserSwitchCallback(any())).then { invocation ->
|
||||||
|
callbacks.remove(invocation.arguments.first() as UserSwitchCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
whenever(controller.currentUserName).thenAnswer { this.currentUserName }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user