From e496b54c2129615a9c7513ee5007d6fa49b31f75 Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Fri, 19 Aug 2022 14:19:37 +0200 Subject: [PATCH] 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 --- .../systemui/qs/FgsManagerController.kt | 154 +++++++++++++----- .../systemui/qs/QSFgsManagerFooter.java | 8 +- .../systemui/qs/dagger/QSFragmentModule.java | 6 + .../systemui/qs/FgsManagerControllerTest.java | 12 +- .../systemui/flags/FakeFeatureFlags.kt | 1 - .../systemui/qs/FakeFgsManagerController.kt | 80 +++++++++ .../systemui/settings/FakeUserTracker.kt | 58 +++++++ .../policy/FakeSecurityController.kt | 118 ++++++++++++++ .../policy/FakeUserInfoController.kt | 58 +++++++ .../MockUserSwitcherControllerWrapper.kt | 59 +++++++ 10 files changed, 499 insertions(+), 55 deletions(-) rename packages/SystemUI/tests/{ => utils}/src/com/android/systemui/flags/FakeFeatureFlags.kt (99%) create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 8f6c3737d6dae..482a1397642b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -68,9 +68,73 @@ import java.util.Objects import java.util.concurrent.Executor import javax.inject.Inject 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 + + /** 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 + + /** + * 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 -class FgsManagerController @Inject constructor( +class FgsManagerControllerImpl @Inject constructor( private val context: Context, @Main private val mainExecutor: Executor, @Background private val backgroundExecutor: Executor, @@ -82,25 +146,32 @@ class FgsManagerController @Inject constructor( private val dialogLaunchAnimator: DialogLaunchAnimator, private val broadcastDispatcher: BroadcastDispatcher, private val dumpManager: DumpManager -) : IForegroundServiceObserver.Stub(), Dumpable { +) : IForegroundServiceObserver.Stub(), Dumpable, FgsManagerController { companion object { 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_SHOW_FOOTER_DOT = false private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true } - var changesSinceDialog = false + override var newChangesSinceDialogWasDismissed = false private set - var isAvailable = false - private set - var showFooterDot = false - private set - var showStopBtnForUserAllowlistedApps = false - private set + val _isAvailable = MutableStateFlow(false) + override val isAvailable: StateFlow = _isAvailable.asStateFlow() + + val _showFooterDot = MutableStateFlow(false) + override val showFooterDot: StateFlow = _showFooterDot.asStateFlow() + + private var showStopBtnForUserAllowlistedApps = false + + override val numRunningPackages: Int + get() { + synchronized(lock) { + return getNumVisiblePackagesLocked() + } + } private val lock = Any() @@ -138,15 +209,7 @@ class FgsManagerController @Inject constructor( } } - interface OnNumberOfPackagesChangedListener { - fun onNumberOfPackagesChanged(numPackages: Int) - } - - interface OnDialogDismissedListener { - fun onDialogDismissed() - } - - fun init() { + override fun init() { synchronized(lock) { if (initialized) { return @@ -165,19 +228,19 @@ class FgsManagerController @Inject constructor( NAMESPACE_SYSTEMUI, backgroundExecutor ) { - isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable) - showFooterDot = - it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot) + _isAvailable.value = it.getBoolean(TASK_MANAGER_ENABLED, _isAvailable.value) + _showFooterDot.value = + it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value) showStopBtnForUserAllowlistedApps = it.getBoolean( TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, showStopBtnForUserAllowlistedApps) } - isAvailable = deviceConfigProxy.getBoolean( + _isAvailable.value = deviceConfigProxy.getBoolean( NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED ) - showFooterDot = deviceConfigProxy.getBoolean( + _showFooterDot.value = deviceConfigProxy.getBoolean( NAMESPACE_SYSTEMUI, TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT ) @@ -232,42 +295,45 @@ class FgsManagerController @Inject constructor( } @GuardedBy("lock") - val onNumberOfPackagesChangedListeners: MutableSet = - mutableSetOf() + private val onNumberOfPackagesChangedListeners = + mutableSetOf() @GuardedBy("lock") - val onDialogDismissedListeners: MutableSet = mutableSetOf() + private val onDialogDismissedListeners = + mutableSetOf() - fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) { + override fun addOnNumberOfPackagesChangedListener( + listener: FgsManagerController.OnNumberOfPackagesChangedListener + ) { synchronized(lock) { onNumberOfPackagesChangedListeners.add(listener) } } - fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) { + override fun removeOnNumberOfPackagesChangedListener( + listener: FgsManagerController.OnNumberOfPackagesChangedListener + ) { synchronized(lock) { onNumberOfPackagesChangedListeners.remove(listener) } } - fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) { + override fun addOnDialogDismissedListener( + listener: FgsManagerController.OnDialogDismissedListener + ) { synchronized(lock) { onDialogDismissedListeners.add(listener) } } - fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) { + override fun removeOnDialogDismissedListener( + listener: FgsManagerController.OnDialogDismissedListener + ) { synchronized(lock) { onDialogDismissedListeners.remove(listener) } } - fun getNumRunningPackages(): Int { - synchronized(lock) { - return getNumVisiblePackagesLocked() - } - } - private fun getNumVisiblePackagesLocked(): Int { return runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId) @@ -278,7 +344,7 @@ class FgsManagerController @Inject constructor( val num = getNumVisiblePackagesLocked() if (num != lastNumberOfVisiblePackages) { lastNumberOfVisiblePackages = num - changesSinceDialog = true + newChangesSinceDialogWasDismissed = true onNumberOfPackagesChangedListeners.forEach { backgroundExecutor.execute { it.onNumberOfPackagesChanged(num) @@ -287,9 +353,7 @@ class FgsManagerController @Inject constructor( } } - @VisibleForTesting - @JvmName("getNumVisibleButtons") - internal fun getNumVisibleButtons(): Int { + override fun visibleButtonsCount(): Int { synchronized(lock) { 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) { if (dialog == null) { @@ -328,7 +392,7 @@ class FgsManagerController @Inject constructor( this.dialog = dialog dialog.setOnDismissListener { - changesSinceDialog = false + newChangesSinceDialogWasDismissed = false synchronized(lock) { this.dialog = null updateAppItemsLocked() @@ -656,7 +720,7 @@ class FgsManagerController @Inject constructor( val pw = IndentingPrintWriter(printwriter) synchronized(lock) { pw.println("current user profiles = $currentProfileIds") - pw.println("changesSinceDialog=$changesSinceDialog") + pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed") pw.println("Running service tokens: [") pw.indentIfPossible { runningServiceTokens.forEach { (userPackage, startTimeAndTokens) -> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java index 875493d73f9eb..372550656d571 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java @@ -149,9 +149,11 @@ public class QSFgsManagerFooter implements View.OnClickListener, mNumberView.setContentDescription(text); if (mFgsManagerController.shouldUpdateFooterVisibility()) { mRootView.setVisibility(mNumPackages > 0 - && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE); - int dotVis = mFgsManagerController.getShowFooterDot() - && mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE; + && mFgsManagerController.isAvailable().getValue() ? View.VISIBLE + : View.GONE); + int dotVis = mFgsManagerController.getShowFooterDot().getValue() + && mFgsManagerController.getNewChangesSinceDialogWasDismissed() + ? View.VISIBLE : View.GONE; mDotView.setVisibility(dotVis); mCollapsedDotView.setVisibility(dotVis); if (mVisibilityChangedListener != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index aa505fb0b6bd4..6f6e4b428e949 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -28,6 +28,8 @@ import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.qs.QS; 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.QSContainerImpl; import com.android.systemui.qs.QSFooter; @@ -194,4 +196,8 @@ public interface QSFragmentModule { ) { return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false); } + + /** */ + @Binds + FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl); } \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java index 3dc10d0ac6cab..7bae115d2edd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java @@ -188,9 +188,9 @@ public class FgsManagerControllerTest extends SysuiTestCase { public void testChangesSinceLastDialog() throws RemoteException { setUserProfiles(0); - Assert.assertFalse(mFmc.getChangesSinceDialog()); + Assert.assertFalse(mFmc.getNewChangesSinceDialogWasDismissed()); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg", 0, true); - Assert.assertTrue(mFmc.getChangesSinceDialog()); + Assert.assertTrue(mFmc.getNewChangesSinceDialogWasDismissed()); } @Test @@ -233,14 +233,14 @@ public class FgsManagerControllerTest extends SysuiTestCase { final Binder binder = new Binder(); setShowStopButtonForUserAllowlistedApps(true); mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true); - Assert.assertEquals(1, mFmc.getNumVisibleButtons()); + Assert.assertEquals(1, mFmc.visibleButtonsCount()); mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, false); - Assert.assertEquals(0, mFmc.getNumVisibleButtons()); + Assert.assertEquals(0, mFmc.visibleButtonsCount()); setShowStopButtonForUserAllowlistedApps(false); mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true); - Assert.assertEquals(0, mFmc.getNumVisibleButtons()); + Assert.assertEquals(0, mFmc.visibleButtonsCount()); } private void setShowStopButtonForUserAllowlistedApps(boolean enable) { @@ -269,7 +269,7 @@ public class FgsManagerControllerTest extends SysuiTestCase { ArgumentCaptor showFgsManagerReceiverArgumentCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); - FgsManagerController result = new FgsManagerController( + FgsManagerController result = new FgsManagerControllerImpl( mContext, mMainExecutor, mBackgroundExecutor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt similarity index 99% rename from packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt rename to packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt index 7b1455cb2e465..b53ad0a3726f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt @@ -56,7 +56,6 @@ class FakeFeatureFlags : FeatureFlags { stringFlags.put(flag.id, value) } - override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id) override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.id) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt new file mode 100644 index 0000000000000..5272585793728 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt @@ -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 = 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 = MutableStateFlow(showFooterDot) + + private val numRunningPackagesListeners = LinkedHashSet() + private val dialogDismissedListeners = LinkedHashSet() + + /** 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 +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt new file mode 100644 index 0000000000000..b2b176420e402 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -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 = emptyList(), + userContentResolver: ContentResolver = MockContentResolver(), + userContext: Context = mock(), + private val onCreateCurrentUserContext: (Context) -> Context = { mock() }, +) : UserTracker { + val callbacks = mutableListOf() + + override val userId: Int = userId + override val userHandle: UserHandle = userHandle + override val userInfo: UserInfo = userInfo + override val userProfiles: List = 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) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt new file mode 100644 index 0000000000000..c6aa3952c4b1b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt @@ -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() + + 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) {} + + 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, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt new file mode 100644 index 0000000000000..32b9a371674fb --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt @@ -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() + + /** 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 = "", + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt new file mode 100644 index 0000000000000..1304a12a5f7fb --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt @@ -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() + + 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 } + } +}