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