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:
Jordan Demeulenaere
2022-08-19 14:19:37 +02:00
parent dcd089d3e7
commit e496b54c21
10 changed files with 499 additions and 55 deletions

View File

@@ -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) ->

View File

@@ -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) {

View File

@@ -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);
} }

View File

@@ -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,

View File

@@ -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)

View File

@@ -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
}

View File

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

View File

@@ -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,
)
}

View File

@@ -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 = "",
)
}

View File

@@ -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 }
}
}