summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java285
2 files changed, 349 insertions, 24 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 3e8cdf3a3592..e5d7b4003297 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -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
@@ -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
@@ -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,
@@ -82,7 +86,8 @@ class FgsManagerController @Inject constructor(
var changesSinceDialog = false
private set
- private var isAvailable = false
+ var isAvailable = false
+ private set
private val lock = Any()
@@ -90,6 +95,12 @@ class FgsManagerController @Inject constructor(
var initialized = false
@GuardedBy("lock")
+ private var lastNumberOfVisiblePackages = 0
+
+ @GuardedBy("lock")
+ private var currentProfileIds = mutableSetOf<Int>()
+
+ @GuardedBy("lock")
private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>()
@GuardedBy("lock")
@@ -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)
}
@@ -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)
@@ -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 {
@@ -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()
}
@@ -209,18 +229,30 @@ class FgsManagerController @Inject constructor(
}
}
- fun isAvailable(): Boolean {
- return isAvailable
- }
-
fun getNumRunningPackages(): Int {
synchronized(lock) {
- return getNumRunningPackagesLocked()
+ return getNumVisiblePackagesLocked()
}
}
- private fun getNumRunningPackagesLocked() =
- runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY }
+ private fun getNumVisiblePackagesLocked(): Int {
+ return runningServiceTokens.keys.count {
+ it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
+ }
+ }
+
+ private fun updateNumberOfVisibleRunningPackagesLocked() {
+ val num = getNumVisiblePackagesLocked()
+ if (num != lastNumberOfVisiblePackages) {
+ lastNumberOfVisiblePackages = num
+ changesSinceDialog = true
+ onNumberOfPackagesChangedListeners.forEach {
+ backgroundExecutor.execute {
+ it.onNumberOfPackagesChanged(num)
+ }
+ }
+ }
+ }
fun shouldUpdateFooterVisibility() = dialog == null
@@ -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)
}
@@ -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
@@ -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
@@ -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("]")
}
@@ -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")
@@ -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 {
@@ -560,8 +598,10 @@ class FgsManagerController @Inject constructor(
pw.indentIfPossible {
runningApps.forEach { (userPackage, runningApp) ->
pw.println("{")
- userPackage.dump(pw)
- runningApp.dump(pw, systemClock)
+ pw.indentIfPossible {
+ userPackage.dump(pw)
+ runningApp.dump(pw, systemClock)
+ }
pw.println("}")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
new file mode 100644
index 000000000000..2927669020c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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);
+ }
+ }
+}