Loading packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +64 −24 Original line number Diff line number Diff line Loading @@ -23,10 +23,12 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.content.pm.UserInfo import android.graphics.drawable.Drawable import android.os.IBinder import android.os.PowerExemptionManager import android.os.RemoteException import android.os.UserHandle import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI import android.text.format.DateUtils import android.util.ArrayMap Loading @@ -51,6 +53,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.indentIfPossible Loading @@ -69,6 +72,7 @@ class FgsManagerController @Inject constructor( private val systemClock: SystemClock, private val activityManager: IActivityManager, private val packageManager: PackageManager, private val userTracker: UserTracker, private val deviceConfigProxy: DeviceConfigProxy, private val dialogLaunchAnimator: DialogLaunchAnimator, private val broadcastDispatcher: BroadcastDispatcher, Loading @@ -82,13 +86,20 @@ class FgsManagerController @Inject constructor( var changesSinceDialog = false private set private var isAvailable = false var isAvailable = false private set private val lock = Any() @GuardedBy("lock") var initialized = false @GuardedBy("lock") private var lastNumberOfVisiblePackages = 0 @GuardedBy("lock") private var currentProfileIds = mutableSetOf<Int>() @GuardedBy("lock") private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>() Loading @@ -101,6 +112,19 @@ class FgsManagerController @Inject constructor( @GuardedBy("lock") private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap() private val userTrackerCallback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) {} override fun onProfilesChanged(profiles: List<UserInfo>) { synchronized(lock) { currentProfileIds.clear() currentProfileIds.addAll(profiles.map { it.id }) lastNumberOfVisiblePackages = 0 updateNumberOfVisibleRunningPackagesLocked() } } } interface OnNumberOfPackagesChangedListener { fun onNumberOfPackagesChanged(numPackages: Int) } Loading @@ -120,6 +144,10 @@ class FgsManagerController @Inject constructor( e.rethrowFromSystemServer() } userTracker.addCallback(userTrackerCallback, backgroundExecutor) currentProfileIds.addAll(userTracker.userProfiles.map { it.id }) deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, backgroundExecutor) { isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable) Loading Loading @@ -153,10 +181,9 @@ class FgsManagerController @Inject constructor( isForeground: Boolean ) { synchronized(lock) { val numPackagesBefore = getNumRunningPackagesLocked() val userPackageKey = UserPackage(userId, packageName) if (isForeground) { runningServiceTokens.getOrPut(userPackageKey, { StartTimeAndTokens(systemClock) }) runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) } .addToken(token) } else { if (runningServiceTokens[userPackageKey]?.also { Loading @@ -165,14 +192,7 @@ class FgsManagerController @Inject constructor( } } val numPackagesAfter = getNumRunningPackagesLocked() if (numPackagesAfter != numPackagesBefore) { changesSinceDialog = true onNumberOfPackagesChangedListeners.forEach { backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) } } } updateNumberOfVisibleRunningPackagesLocked() updateAppItemsLocked() } Loading Loading @@ -209,18 +229,30 @@ class FgsManagerController @Inject constructor( } } fun isAvailable(): Boolean { return isAvailable } fun getNumRunningPackages(): Int { synchronized(lock) { return getNumRunningPackagesLocked() return getNumVisiblePackagesLocked() } } private fun getNumVisiblePackagesLocked(): Int { return runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId) } } private fun getNumRunningPackagesLocked() = runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY } private fun updateNumberOfVisibleRunningPackagesLocked() { val num = getNumVisiblePackagesLocked() if (num != lastNumberOfVisiblePackages) { lastNumberOfVisiblePackages = num changesSinceDialog = true onNumberOfPackagesChangedListeners.forEach { backgroundExecutor.execute { it.onNumberOfPackagesChanged(num) } } } } fun shouldUpdateFooterVisibility() = dialog == null Loading Loading @@ -289,7 +321,9 @@ class FgsManagerController @Inject constructor( val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) runningApps[it] = RunningApp(it.userId, it.packageName, runningServiceTokens[it]!!.startTime, it.uiControl, ai.loadLabel(packageManager), ai.loadIcon(packageManager)) packageManager.getApplicationLabel(ai), packageManager.getUserBadgedIcon( packageManager.getApplicationIcon(ai), UserHandle.of(it.userId))) logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted) } Loading Loading @@ -404,6 +438,7 @@ class FgsManagerController @Inject constructor( val packageName: String ) { val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) } var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED private var uiControlInitialized = false var uiControl: UIControl = UIControl.NORMAL Loading @@ -416,7 +451,9 @@ class FgsManagerController @Inject constructor( private set fun updateUiControl() { uiControl = when (activityManager.getBackgroundRestrictionExemptionReason(uid)) { backgroundRestrictionExemptionReason = activityManager.getBackgroundRestrictionExemptionReason(uid) uiControl = when (backgroundRestrictionExemptionReason) { PowerExemptionManager.REASON_SYSTEM_UID, PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY Loading Loading @@ -448,7 +485,7 @@ class FgsManagerController @Inject constructor( pw.indentIfPossible { pw.println("userId=$userId") pw.println("packageName=$packageName") pw.println("uiControl=$uiControl") pw.println("uiControl=$uiControl (reason=$backgroundRestrictionExemptionReason)") } pw.println("]") } Loading Loading @@ -525,7 +562,7 @@ class FgsManagerController @Inject constructor( pw.println("userId=$userId") pw.println("packageName=$packageName") pw.println("timeStarted=$timeStarted (time since start =" + " ${systemClock.elapsedRealtime() - timeStarted}ms)\"") " ${systemClock.elapsedRealtime() - timeStarted}ms)") pw.println("uiControl=$uiControl") pw.println("appLabel=$appLabel") pw.println("icon=$icon") Loading @@ -542,6 +579,7 @@ class FgsManagerController @Inject constructor( override fun dump(printwriter: PrintWriter, args: Array<out String>) { val pw = IndentingPrintWriter(printwriter) synchronized(lock) { pw.println("current user profiles = $currentProfileIds") pw.println("changesSinceDialog=$changesSinceDialog") pw.println("Running service tokens: [") pw.indentIfPossible { Loading @@ -560,8 +598,10 @@ class FgsManagerController @Inject constructor( pw.indentIfPossible { runningApps.forEach { (userPackage, runningApp) -> pw.println("{") pw.indentIfPossible { userPackage.dump(pw) runningApp.dump(pw, systemClock) } pw.println("}") } } Loading packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java 0 → 100644 +285 −0 Original line number Diff line number Diff line /* * 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 static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.IActivityManager; import android.app.IForegroundServiceObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Binder; import android.os.RemoteException; import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest public class FgsManagerControllerTest extends SysuiTestCase { FakeSystemClock mSystemClock; FakeExecutor mMainExecutor; FakeExecutor mBackgroundExecutor; DeviceConfigProxyFake mDeviceConfigProxyFake; @Mock IActivityManager mIActivityManager; @Mock PackageManager mPackageManager; @Mock UserTracker mUserTracker; @Mock DialogLaunchAnimator mDialogLaunchAnimator; @Mock BroadcastDispatcher mBroadcastDispatcher; @Mock DumpManager mDumpManager; private FgsManagerController mFmc; private IForegroundServiceObserver mIForegroundServiceObserver; private UserTracker.Callback mUserTrackerCallback; private BroadcastReceiver mShowFgsManagerReceiver; private List<UserInfo> mUserProfiles; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mDeviceConfigProxyFake = new DeviceConfigProxyFake(); mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, "true", false); mSystemClock = new FakeSystemClock(); mMainExecutor = new FakeExecutor(mSystemClock); mBackgroundExecutor = new FakeExecutor(mSystemClock); mUserProfiles = new ArrayList<>(); Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles(); mFmc = createFgsManagerController(); } @Test public void testNumPackages() throws RemoteException { setUserProfiles(0); Binder b1 = new Binder(); Binder b2 = new Binder(); Assert.assertEquals(0, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true); Assert.assertEquals(2, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false); Assert.assertEquals(0, mFmc.getNumRunningPackages()); } @Test public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException { setUserProfiles(0); // Different tokens == different services Binder b1 = new Binder(); Binder b2 = new Binder(); Assert.assertEquals(0, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg1", 0, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg1", 0, false); Assert.assertEquals(0, mFmc.getNumRunningPackages()); } @Test public void testNumPackagesListener() throws RemoteException { setUserProfiles(0); FgsManagerController.OnNumberOfPackagesChangedListener onNumberOfPackagesChangedListener = Mockito.mock(FgsManagerController.OnNumberOfPackagesChangedListener.class); mFmc.addOnNumberOfPackagesChangedListener(onNumberOfPackagesChangedListener); Binder b1 = new Binder(); Binder b2 = new Binder(); verify(onNumberOfPackagesChangedListener, never()).onNumberOfPackagesChanged(anyInt()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(1); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(2); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener, times(2)).onNumberOfPackagesChanged(1); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(0); } @Test public void testChangesSinceLastDialog() throws RemoteException { setUserProfiles(0); Assert.assertFalse(mFmc.getChangesSinceDialog()); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg", 0, true); Assert.assertTrue(mFmc.getChangesSinceDialog()); } @Test public void testProfilePackagesCounted() throws RemoteException { setUserProfiles(0, 10); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true); Assert.assertEquals(2, mFmc.getNumRunningPackages()); } @Test public void testSecondaryUserPackagesAreNotCounted() throws RemoteException { setUserProfiles(0); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); } @Test public void testSecondaryUserPackagesAreCountedWhenUserSwitch() throws RemoteException { setUserProfiles(0); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg3", 10, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); setUserProfiles(10); Assert.assertEquals(2, mFmc.getNumRunningPackages()); } FgsManagerController createFgsManagerController() throws RemoteException { ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor = ArgumentCaptor.forClass(IForegroundServiceObserver.class); ArgumentCaptor<UserTracker.Callback> userTrackerCallbackArgumentCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class); ArgumentCaptor<BroadcastReceiver> showFgsManagerReceiverArgumentCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); FgsManagerController result = new FgsManagerController( mContext, mMainExecutor, mBackgroundExecutor, mSystemClock, mIActivityManager, mPackageManager, mUserTracker, mDeviceConfigProxyFake, mDialogLaunchAnimator, mBroadcastDispatcher, mDumpManager ); result.init(); verify(mIActivityManager).registerForegroundServiceObserver( iForegroundServiceObserverArgumentCaptor.capture() ); verify(mUserTracker).addCallback( userTrackerCallbackArgumentCaptor.capture(), ArgumentMatchers.eq(mBackgroundExecutor) ); verify(mBroadcastDispatcher).registerReceiver( showFgsManagerReceiverArgumentCaptor.capture(), argThat(fltr -> fltr.matchAction(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER)), eq(mMainExecutor), isNull(), eq(Context.RECEIVER_NOT_EXPORTED), isNull() ); mIForegroundServiceObserver = iForegroundServiceObserverArgumentCaptor.getValue(); mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue(); mShowFgsManagerReceiver = showFgsManagerReceiverArgumentCaptor.getValue(); return result; } private void setUserProfiles(int current, int... profileUserIds) { mUserProfiles.clear(); mUserProfiles.add(new UserInfo(current, "current:" + current, 0)); for (int id : profileUserIds) { mUserProfiles.add(new UserInfo(id, "profile:" + id, 0)); } if (mUserTrackerCallback != null) { mUserTrackerCallback.onUserChanged(current, mock(Context.class)); mUserTrackerCallback.onProfilesChanged(mUserProfiles); } } } Loading
packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +64 −24 Original line number Diff line number Diff line Loading @@ -23,10 +23,12 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.content.pm.UserInfo import android.graphics.drawable.Drawable import android.os.IBinder import android.os.PowerExemptionManager import android.os.RemoteException import android.os.UserHandle import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI import android.text.format.DateUtils import android.util.ArrayMap Loading @@ -51,6 +53,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.indentIfPossible Loading @@ -69,6 +72,7 @@ class FgsManagerController @Inject constructor( private val systemClock: SystemClock, private val activityManager: IActivityManager, private val packageManager: PackageManager, private val userTracker: UserTracker, private val deviceConfigProxy: DeviceConfigProxy, private val dialogLaunchAnimator: DialogLaunchAnimator, private val broadcastDispatcher: BroadcastDispatcher, Loading @@ -82,13 +86,20 @@ class FgsManagerController @Inject constructor( var changesSinceDialog = false private set private var isAvailable = false var isAvailable = false private set private val lock = Any() @GuardedBy("lock") var initialized = false @GuardedBy("lock") private var lastNumberOfVisiblePackages = 0 @GuardedBy("lock") private var currentProfileIds = mutableSetOf<Int>() @GuardedBy("lock") private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>() Loading @@ -101,6 +112,19 @@ class FgsManagerController @Inject constructor( @GuardedBy("lock") private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap() private val userTrackerCallback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) {} override fun onProfilesChanged(profiles: List<UserInfo>) { synchronized(lock) { currentProfileIds.clear() currentProfileIds.addAll(profiles.map { it.id }) lastNumberOfVisiblePackages = 0 updateNumberOfVisibleRunningPackagesLocked() } } } interface OnNumberOfPackagesChangedListener { fun onNumberOfPackagesChanged(numPackages: Int) } Loading @@ -120,6 +144,10 @@ class FgsManagerController @Inject constructor( e.rethrowFromSystemServer() } userTracker.addCallback(userTrackerCallback, backgroundExecutor) currentProfileIds.addAll(userTracker.userProfiles.map { it.id }) deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, backgroundExecutor) { isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable) Loading Loading @@ -153,10 +181,9 @@ class FgsManagerController @Inject constructor( isForeground: Boolean ) { synchronized(lock) { val numPackagesBefore = getNumRunningPackagesLocked() val userPackageKey = UserPackage(userId, packageName) if (isForeground) { runningServiceTokens.getOrPut(userPackageKey, { StartTimeAndTokens(systemClock) }) runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) } .addToken(token) } else { if (runningServiceTokens[userPackageKey]?.also { Loading @@ -165,14 +192,7 @@ class FgsManagerController @Inject constructor( } } val numPackagesAfter = getNumRunningPackagesLocked() if (numPackagesAfter != numPackagesBefore) { changesSinceDialog = true onNumberOfPackagesChangedListeners.forEach { backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) } } } updateNumberOfVisibleRunningPackagesLocked() updateAppItemsLocked() } Loading Loading @@ -209,18 +229,30 @@ class FgsManagerController @Inject constructor( } } fun isAvailable(): Boolean { return isAvailable } fun getNumRunningPackages(): Int { synchronized(lock) { return getNumRunningPackagesLocked() return getNumVisiblePackagesLocked() } } private fun getNumVisiblePackagesLocked(): Int { return runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId) } } private fun getNumRunningPackagesLocked() = runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY } private fun updateNumberOfVisibleRunningPackagesLocked() { val num = getNumVisiblePackagesLocked() if (num != lastNumberOfVisiblePackages) { lastNumberOfVisiblePackages = num changesSinceDialog = true onNumberOfPackagesChangedListeners.forEach { backgroundExecutor.execute { it.onNumberOfPackagesChanged(num) } } } } fun shouldUpdateFooterVisibility() = dialog == null Loading Loading @@ -289,7 +321,9 @@ class FgsManagerController @Inject constructor( val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) runningApps[it] = RunningApp(it.userId, it.packageName, runningServiceTokens[it]!!.startTime, it.uiControl, ai.loadLabel(packageManager), ai.loadIcon(packageManager)) packageManager.getApplicationLabel(ai), packageManager.getUserBadgedIcon( packageManager.getApplicationIcon(ai), UserHandle.of(it.userId))) logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted) } Loading Loading @@ -404,6 +438,7 @@ class FgsManagerController @Inject constructor( val packageName: String ) { val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) } var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED private var uiControlInitialized = false var uiControl: UIControl = UIControl.NORMAL Loading @@ -416,7 +451,9 @@ class FgsManagerController @Inject constructor( private set fun updateUiControl() { uiControl = when (activityManager.getBackgroundRestrictionExemptionReason(uid)) { backgroundRestrictionExemptionReason = activityManager.getBackgroundRestrictionExemptionReason(uid) uiControl = when (backgroundRestrictionExemptionReason) { PowerExemptionManager.REASON_SYSTEM_UID, PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY Loading Loading @@ -448,7 +485,7 @@ class FgsManagerController @Inject constructor( pw.indentIfPossible { pw.println("userId=$userId") pw.println("packageName=$packageName") pw.println("uiControl=$uiControl") pw.println("uiControl=$uiControl (reason=$backgroundRestrictionExemptionReason)") } pw.println("]") } Loading Loading @@ -525,7 +562,7 @@ class FgsManagerController @Inject constructor( pw.println("userId=$userId") pw.println("packageName=$packageName") pw.println("timeStarted=$timeStarted (time since start =" + " ${systemClock.elapsedRealtime() - timeStarted}ms)\"") " ${systemClock.elapsedRealtime() - timeStarted}ms)") pw.println("uiControl=$uiControl") pw.println("appLabel=$appLabel") pw.println("icon=$icon") Loading @@ -542,6 +579,7 @@ class FgsManagerController @Inject constructor( override fun dump(printwriter: PrintWriter, args: Array<out String>) { val pw = IndentingPrintWriter(printwriter) synchronized(lock) { pw.println("current user profiles = $currentProfileIds") pw.println("changesSinceDialog=$changesSinceDialog") pw.println("Running service tokens: [") pw.indentIfPossible { Loading @@ -560,8 +598,10 @@ class FgsManagerController @Inject constructor( pw.indentIfPossible { runningApps.forEach { (userPackage, runningApp) -> pw.println("{") pw.indentIfPossible { userPackage.dump(pw) runningApp.dump(pw, systemClock) } pw.println("}") } } Loading
packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java 0 → 100644 +285 −0 Original line number Diff line number Diff line /* * 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 static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.IActivityManager; import android.app.IForegroundServiceObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Binder; import android.os.RemoteException; import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest public class FgsManagerControllerTest extends SysuiTestCase { FakeSystemClock mSystemClock; FakeExecutor mMainExecutor; FakeExecutor mBackgroundExecutor; DeviceConfigProxyFake mDeviceConfigProxyFake; @Mock IActivityManager mIActivityManager; @Mock PackageManager mPackageManager; @Mock UserTracker mUserTracker; @Mock DialogLaunchAnimator mDialogLaunchAnimator; @Mock BroadcastDispatcher mBroadcastDispatcher; @Mock DumpManager mDumpManager; private FgsManagerController mFmc; private IForegroundServiceObserver mIForegroundServiceObserver; private UserTracker.Callback mUserTrackerCallback; private BroadcastReceiver mShowFgsManagerReceiver; private List<UserInfo> mUserProfiles; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mDeviceConfigProxyFake = new DeviceConfigProxyFake(); mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, "true", false); mSystemClock = new FakeSystemClock(); mMainExecutor = new FakeExecutor(mSystemClock); mBackgroundExecutor = new FakeExecutor(mSystemClock); mUserProfiles = new ArrayList<>(); Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles(); mFmc = createFgsManagerController(); } @Test public void testNumPackages() throws RemoteException { setUserProfiles(0); Binder b1 = new Binder(); Binder b2 = new Binder(); Assert.assertEquals(0, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true); Assert.assertEquals(2, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false); Assert.assertEquals(0, mFmc.getNumRunningPackages()); } @Test public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException { setUserProfiles(0); // Different tokens == different services Binder b1 = new Binder(); Binder b2 = new Binder(); Assert.assertEquals(0, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg1", 0, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false); Assert.assertEquals(1, mFmc.getNumRunningPackages()); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg1", 0, false); Assert.assertEquals(0, mFmc.getNumRunningPackages()); } @Test public void testNumPackagesListener() throws RemoteException { setUserProfiles(0); FgsManagerController.OnNumberOfPackagesChangedListener onNumberOfPackagesChangedListener = Mockito.mock(FgsManagerController.OnNumberOfPackagesChangedListener.class); mFmc.addOnNumberOfPackagesChangedListener(onNumberOfPackagesChangedListener); Binder b1 = new Binder(); Binder b2 = new Binder(); verify(onNumberOfPackagesChangedListener, never()).onNumberOfPackagesChanged(anyInt()); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(1); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(2); mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener, times(2)).onNumberOfPackagesChanged(1); mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false); mBackgroundExecutor.advanceClockToLast(); mBackgroundExecutor.runAllReady(); verify(onNumberOfPackagesChangedListener).onNumberOfPackagesChanged(0); } @Test public void testChangesSinceLastDialog() throws RemoteException { setUserProfiles(0); Assert.assertFalse(mFmc.getChangesSinceDialog()); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg", 0, true); Assert.assertTrue(mFmc.getChangesSinceDialog()); } @Test public void testProfilePackagesCounted() throws RemoteException { setUserProfiles(0, 10); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true); Assert.assertEquals(2, mFmc.getNumRunningPackages()); } @Test public void testSecondaryUserPackagesAreNotCounted() throws RemoteException { setUserProfiles(0); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); } @Test public void testSecondaryUserPackagesAreCountedWhenUserSwitch() throws RemoteException { setUserProfiles(0); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg1", 0, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg2", 10, true); mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg3", 10, true); Assert.assertEquals(1, mFmc.getNumRunningPackages()); setUserProfiles(10); Assert.assertEquals(2, mFmc.getNumRunningPackages()); } FgsManagerController createFgsManagerController() throws RemoteException { ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor = ArgumentCaptor.forClass(IForegroundServiceObserver.class); ArgumentCaptor<UserTracker.Callback> userTrackerCallbackArgumentCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class); ArgumentCaptor<BroadcastReceiver> showFgsManagerReceiverArgumentCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); FgsManagerController result = new FgsManagerController( mContext, mMainExecutor, mBackgroundExecutor, mSystemClock, mIActivityManager, mPackageManager, mUserTracker, mDeviceConfigProxyFake, mDialogLaunchAnimator, mBroadcastDispatcher, mDumpManager ); result.init(); verify(mIActivityManager).registerForegroundServiceObserver( iForegroundServiceObserverArgumentCaptor.capture() ); verify(mUserTracker).addCallback( userTrackerCallbackArgumentCaptor.capture(), ArgumentMatchers.eq(mBackgroundExecutor) ); verify(mBroadcastDispatcher).registerReceiver( showFgsManagerReceiverArgumentCaptor.capture(), argThat(fltr -> fltr.matchAction(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER)), eq(mMainExecutor), isNull(), eq(Context.RECEIVER_NOT_EXPORTED), isNull() ); mIForegroundServiceObserver = iForegroundServiceObserverArgumentCaptor.getValue(); mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue(); mShowFgsManagerReceiver = showFgsManagerReceiverArgumentCaptor.getValue(); return result; } private void setUserProfiles(int current, int... profileUserIds) { mUserProfiles.clear(); mUserProfiles.add(new UserInfo(current, "current:" + current, 0)); for (int id : profileUserIds) { mUserProfiles.add(new UserInfo(id, "profile:" + id, 0)); } if (mUserTrackerCallback != null) { mUserTrackerCallback.onUserChanged(current, mock(Context.class)); mUserTrackerCallback.onProfilesChanged(mUserProfiles); } } }