Merge "Only show packages for the current user in FgsManager" into tm-dev

This commit is contained in:
TreeHugger Robot
2022-05-13 03:29:43 +00:00
committed by Android (Google) Code Review
2 changed files with 349 additions and 24 deletions

View File

@@ -23,10 +23,12 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.IBinder import android.os.IBinder
import android.os.PowerExemptionManager import android.os.PowerExemptionManager
import android.os.RemoteException import android.os.RemoteException
import android.os.UserHandle
import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.ArrayMap 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.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.indentIfPossible import com.android.systemui.util.indentIfPossible
@@ -69,6 +72,7 @@ class FgsManagerController @Inject constructor(
private val systemClock: SystemClock, private val systemClock: SystemClock,
private val activityManager: IActivityManager, private val activityManager: IActivityManager,
private val packageManager: PackageManager, private val packageManager: PackageManager,
private val userTracker: UserTracker,
private val deviceConfigProxy: DeviceConfigProxy, private val deviceConfigProxy: DeviceConfigProxy,
private val dialogLaunchAnimator: DialogLaunchAnimator, private val dialogLaunchAnimator: DialogLaunchAnimator,
private val broadcastDispatcher: BroadcastDispatcher, private val broadcastDispatcher: BroadcastDispatcher,
@@ -82,13 +86,20 @@ class FgsManagerController @Inject constructor(
var changesSinceDialog = false var changesSinceDialog = false
private set private set
private var isAvailable = false var isAvailable = false
private set
private val lock = Any() private val lock = Any()
@GuardedBy("lock") @GuardedBy("lock")
var initialized = false var initialized = false
@GuardedBy("lock")
private var lastNumberOfVisiblePackages = 0
@GuardedBy("lock")
private var currentProfileIds = mutableSetOf<Int>()
@GuardedBy("lock") @GuardedBy("lock")
private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>() private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>()
@@ -101,6 +112,19 @@ class FgsManagerController @Inject constructor(
@GuardedBy("lock") @GuardedBy("lock")
private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap() 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 { interface OnNumberOfPackagesChangedListener {
fun onNumberOfPackagesChanged(numPackages: Int) fun onNumberOfPackagesChanged(numPackages: Int)
} }
@@ -120,6 +144,10 @@ class FgsManagerController @Inject constructor(
e.rethrowFromSystemServer() e.rethrowFromSystemServer()
} }
userTracker.addCallback(userTrackerCallback, backgroundExecutor)
currentProfileIds.addAll(userTracker.userProfiles.map { it.id })
deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
backgroundExecutor) { backgroundExecutor) {
isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable) isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
@@ -153,10 +181,9 @@ class FgsManagerController @Inject constructor(
isForeground: Boolean isForeground: Boolean
) { ) {
synchronized(lock) { synchronized(lock) {
val numPackagesBefore = getNumRunningPackagesLocked()
val userPackageKey = UserPackage(userId, packageName) val userPackageKey = UserPackage(userId, packageName)
if (isForeground) { if (isForeground) {
runningServiceTokens.getOrPut(userPackageKey, { StartTimeAndTokens(systemClock) }) runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) }
.addToken(token) .addToken(token)
} else { } else {
if (runningServiceTokens[userPackageKey]?.also { if (runningServiceTokens[userPackageKey]?.also {
@@ -165,14 +192,7 @@ class FgsManagerController @Inject constructor(
} }
} }
val numPackagesAfter = getNumRunningPackagesLocked() updateNumberOfVisibleRunningPackagesLocked()
if (numPackagesAfter != numPackagesBefore) {
changesSinceDialog = true
onNumberOfPackagesChangedListeners.forEach {
backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) }
}
}
updateAppItemsLocked() updateAppItemsLocked()
} }
@@ -209,18 +229,30 @@ class FgsManagerController @Inject constructor(
} }
} }
fun isAvailable(): Boolean {
return isAvailable
}
fun getNumRunningPackages(): Int { fun getNumRunningPackages(): Int {
synchronized(lock) { synchronized(lock) {
return getNumRunningPackagesLocked() return getNumVisiblePackagesLocked()
} }
} }
private fun getNumRunningPackagesLocked() = private fun getNumVisiblePackagesLocked(): Int {
runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY } 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 fun shouldUpdateFooterVisibility() = dialog == null
@@ -289,7 +321,9 @@ class FgsManagerController @Inject constructor(
val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
runningApps[it] = RunningApp(it.userId, it.packageName, runningApps[it] = RunningApp(it.userId, it.packageName,
runningServiceTokens[it]!!.startTime, it.uiControl, 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) logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted)
} }
@@ -404,6 +438,7 @@ class FgsManagerController @Inject constructor(
val packageName: String val packageName: String
) { ) {
val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) } val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) }
var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED
private var uiControlInitialized = false private var uiControlInitialized = false
var uiControl: UIControl = UIControl.NORMAL var uiControl: UIControl = UIControl.NORMAL
@@ -416,7 +451,9 @@ class FgsManagerController @Inject constructor(
private set private set
fun updateUiControl() { fun updateUiControl() {
uiControl = when (activityManager.getBackgroundRestrictionExemptionReason(uid)) { backgroundRestrictionExemptionReason =
activityManager.getBackgroundRestrictionExemptionReason(uid)
uiControl = when (backgroundRestrictionExemptionReason) {
PowerExemptionManager.REASON_SYSTEM_UID, PowerExemptionManager.REASON_SYSTEM_UID,
PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
@@ -448,7 +485,7 @@ class FgsManagerController @Inject constructor(
pw.indentIfPossible { pw.indentIfPossible {
pw.println("userId=$userId") pw.println("userId=$userId")
pw.println("packageName=$packageName") pw.println("packageName=$packageName")
pw.println("uiControl=$uiControl") pw.println("uiControl=$uiControl (reason=$backgroundRestrictionExemptionReason)")
} }
pw.println("]") pw.println("]")
} }
@@ -525,7 +562,7 @@ class FgsManagerController @Inject constructor(
pw.println("userId=$userId") pw.println("userId=$userId")
pw.println("packageName=$packageName") pw.println("packageName=$packageName")
pw.println("timeStarted=$timeStarted (time since start =" + pw.println("timeStarted=$timeStarted (time since start =" +
" ${systemClock.elapsedRealtime() - timeStarted}ms)\"") " ${systemClock.elapsedRealtime() - timeStarted}ms)")
pw.println("uiControl=$uiControl") pw.println("uiControl=$uiControl")
pw.println("appLabel=$appLabel") pw.println("appLabel=$appLabel")
pw.println("icon=$icon") pw.println("icon=$icon")
@@ -542,6 +579,7 @@ class FgsManagerController @Inject constructor(
override fun dump(printwriter: PrintWriter, args: Array<out String>) { override fun dump(printwriter: PrintWriter, args: Array<out String>) {
val pw = IndentingPrintWriter(printwriter) val pw = IndentingPrintWriter(printwriter)
synchronized(lock) { synchronized(lock) {
pw.println("current user profiles = $currentProfileIds")
pw.println("changesSinceDialog=$changesSinceDialog") pw.println("changesSinceDialog=$changesSinceDialog")
pw.println("Running service tokens: [") pw.println("Running service tokens: [")
pw.indentIfPossible { pw.indentIfPossible {
@@ -560,8 +598,10 @@ class FgsManagerController @Inject constructor(
pw.indentIfPossible { pw.indentIfPossible {
runningApps.forEach { (userPackage, runningApp) -> runningApps.forEach { (userPackage, runningApp) ->
pw.println("{") pw.println("{")
userPackage.dump(pw) pw.indentIfPossible {
runningApp.dump(pw, systemClock) userPackage.dump(pw)
runningApp.dump(pw, systemClock)
}
pw.println("}") pw.println("}")
} }
} }

View File

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