Merge "Add logic to show apps running certain jobs in Task Manager." into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-12-03 06:20:53 +00:00
committed by Android (Google) Code Review
10 changed files with 600 additions and 50 deletions

View File

@@ -16,7 +16,10 @@
package android.app;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.job.IJobScheduler;
import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.app.job.JobSnapshot;
@@ -119,4 +122,28 @@ public class JobSchedulerImpl extends JobScheduler {
return null;
}
}
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
// TODO(255767350): implement
}
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
// TODO(255767350): implement
}
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
// TODO(255767350): implement
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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 android.app.job;
import android.app.job.UserVisibleJobSummary;
/**
* IPC protocol to know about user-visible job activity.
*
* @hide
*/
oneway interface IUserVisibleJobObserver {
/**
* Notify the client of all changes to a user-visible jobs' state.
* @param summary A token/summary that uniquely identifies and details a single running job
* @param isRunning whether the job is currently running or not
*/
void onUserVisibleJobStateChanged(in UserVisibleJobSummary summary, boolean isRunning);
}

View File

@@ -237,4 +237,32 @@ public abstract class JobScheduler {
*/
@SuppressWarnings("HiddenAbstractMethod")
public abstract List<JobSnapshot> getAllJobSnapshots();
}
/**
* @hide
*/
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@SuppressWarnings("HiddenAbstractMethod")
public abstract void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer);
/**
* @hide
*/
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@SuppressWarnings("HiddenAbstractMethod")
public abstract void unregisterUserVisibleJobObserver(
@NonNull IUserVisibleJobObserver observer);
/**
* @hide
*/
@RequiresPermission(allOf = {
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@SuppressWarnings("HiddenAbstractMethod")
public abstract void stopUserVisibleJobsForUser(@NonNull String packageName, int userId);
}

View File

@@ -0,0 +1,19 @@
/**
* 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 android.app.job;
parcelable UserVisibleJobSummary;

View File

@@ -0,0 +1,122 @@
/*
* 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 android.app.job;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Summary of a scheduled job that the user is meant to be aware of.
*
* @hide
*/
public class UserVisibleJobSummary implements Parcelable {
private final int mCallingUid;
private final int mSourceUserId;
@NonNull
private final String mSourcePackageName;
private final int mJobId;
public UserVisibleJobSummary(int callingUid, int sourceUserId,
@NonNull String sourcePackageName, int jobId) {
mCallingUid = callingUid;
mSourceUserId = sourceUserId;
mSourcePackageName = sourcePackageName;
mJobId = jobId;
}
protected UserVisibleJobSummary(Parcel in) {
mCallingUid = in.readInt();
mSourceUserId = in.readInt();
mSourcePackageName = in.readString();
mJobId = in.readInt();
}
public int getCallingUid() {
return mCallingUid;
}
public int getJobId() {
return mJobId;
}
public int getSourceUserId() {
return mSourceUserId;
}
public String getSourcePackageName() {
return mSourcePackageName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UserVisibleJobSummary)) return false;
UserVisibleJobSummary that = (UserVisibleJobSummary) o;
return mCallingUid == that.mCallingUid
&& mSourceUserId == that.mSourceUserId
&& mSourcePackageName.equals(that.mSourcePackageName)
&& mJobId == that.mJobId;
}
@Override
public int hashCode() {
int result = 0;
result = 31 * result + mCallingUid;
result = 31 * result + mSourceUserId;
result = 31 * result + mSourcePackageName.hashCode();
result = 31 * result + mJobId;
return result;
}
@Override
public String toString() {
return "UserVisibleJobSummary{"
+ "callingUid=" + mCallingUid
+ ", sourceUserId=" + mSourceUserId
+ ", sourcePackageName='" + mSourcePackageName + "'"
+ ", jobId=" + mJobId
+ "}";
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mCallingUid);
dest.writeInt(mSourceUserId);
dest.writeString(mSourcePackageName);
dest.writeInt(mJobId);
}
public static final Creator<UserVisibleJobSummary> CREATOR =
new Creator<UserVisibleJobSummary>() {
@Override
public UserVisibleJobSummary createFromParcel(Parcel in) {
return new UserVisibleJobSummary(in);
}
@Override
public UserVisibleJobSummary[] newArray(int size) {
return new UserVisibleJobSummary[size];
}
};
}

View File

@@ -555,6 +555,12 @@ public final class SystemUiDeviceConfigFlags {
public static final String TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS =
"show_stop_button_for_user_allowlisted_apps";
/**
* (boolean) Whether the task manager should show apps running user-visible jobs.
*/
public static final String TASK_MANAGER_SHOW_USER_VISIBLE_JOBS =
"task_manager_show_user_visible_jobs";
/**
* (boolean) Whether to show notification volume control slider separate from ring.
*/

View File

@@ -31,6 +31,7 @@ import android.app.StatsManager;
import android.app.UiModeManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.app.job.JobScheduler;
import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager;
@@ -277,6 +278,12 @@ public class FrameworkServicesModule {
context.getSystemService(BiometricManager.class);
}
@Provides
@Singleton
static JobScheduler provideJobScheduler(Context context) {
return context.getSystemService(JobScheduler.class);
}
@Provides
@Singleton
static InteractionJankMonitor provideInteractionJankMonitor() {

View File

@@ -18,6 +18,9 @@ package com.android.systemui.qs
import android.app.IActivityManager
import android.app.IForegroundServiceObserver
import android.app.job.IUserVisibleJobObserver
import android.app.job.JobScheduler
import android.app.job.UserVisibleJobSummary
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -47,6 +50,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -92,6 +96,8 @@ interface FgsManagerController {
*/
val showFooterDot: StateFlow<Boolean>
val includesUserVisibleJobs: Boolean
/**
* Initialize this controller. This should be called once, before this controller is used for
* the first time.
@@ -141,19 +147,21 @@ class FgsManagerControllerImpl @Inject constructor(
@Background private val backgroundExecutor: Executor,
private val systemClock: SystemClock,
private val activityManager: IActivityManager,
private val jobScheduler: JobScheduler,
private val packageManager: PackageManager,
private val userTracker: UserTracker,
private val deviceConfigProxy: DeviceConfigProxy,
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager
) : IForegroundServiceObserver.Stub(), Dumpable, FgsManagerController {
) : Dumpable, FgsManagerController {
companion object {
private const val INTERACTION_JANK_TAG = "active_background_apps"
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
private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = false
}
override var newChangesSinceDialogWasDismissed = false
@@ -167,6 +175,11 @@ class FgsManagerControllerImpl @Inject constructor(
private var showStopBtnForUserAllowlistedApps = false
private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
override val includesUserVisibleJobs: Boolean
get() = showUserVisibleJobs
override val numRunningPackages: Int
get() {
synchronized(lock) {
@@ -186,7 +199,7 @@ class FgsManagerControllerImpl @Inject constructor(
private var currentProfileIds = mutableSetOf<Int>()
@GuardedBy("lock")
private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>()
private val runningTaskIdentifiers = mutableMapOf<UserPackage, StartTimeAndIdentifiers>()
@GuardedBy("lock")
private var dialog: SystemUIDialog? = null
@@ -210,13 +223,29 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
private val foregroundServiceObserver = ForegroundServiceObserver()
private val userVisibleJobObserver = UserVisibleJobObserver()
override fun init() {
synchronized(lock) {
if (initialized) {
return
}
showUserVisibleJobs = deviceConfigProxy.getBoolean(
NAMESPACE_SYSTEMUI,
TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
try {
activityManager.registerForegroundServiceObserver(this)
activityManager.registerForegroundServiceObserver(foregroundServiceObserver)
// Clumping FGS and user-visible jobs here and showing a single entry and button
// for them is the easiest way to get user-visible jobs showing in Task Manager.
// Ideally, we would have dedicated UI in task manager for the user-visible jobs.
// TODO(255768978): distinguish jobs from FGS and give users more control
if (showUserVisibleJobs) {
jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver)
}
} catch (e: RemoteException) {
e.rethrowFromSystemServer()
}
@@ -235,6 +264,12 @@ class FgsManagerControllerImpl @Inject constructor(
showStopBtnForUserAllowlistedApps = it.getBoolean(
TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
showStopBtnForUserAllowlistedApps)
var wasShowingUserVisibleJobs = showUserVisibleJobs
showUserVisibleJobs = it.getBoolean(
TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
onShowUserVisibleJobsFlagChanged()
}
}
_isAvailable.value = deviceConfigProxy.getBoolean(
@@ -269,32 +304,6 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
override fun onForegroundStateChanged(
token: IBinder,
packageName: String,
userId: Int,
isForeground: Boolean
) {
synchronized(lock) {
val userPackageKey = UserPackage(userId, packageName)
if (isForeground) {
runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) }
.addToken(token)
} else {
if (runningServiceTokens[userPackageKey]?.also {
it.removeToken(token)
}?.isEmpty() == true
) {
runningServiceTokens.remove(userPackageKey)
}
}
updateNumberOfVisibleRunningPackagesLocked()
updateAppItemsLocked()
}
}
@GuardedBy("lock")
private val onNumberOfPackagesChangedListeners =
mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>()
@@ -336,7 +345,7 @@ class FgsManagerControllerImpl @Inject constructor(
}
private fun getNumVisiblePackagesLocked(): Int {
return runningServiceTokens.keys.count {
return runningTaskIdentifiers.keys.count {
it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
}
}
@@ -361,7 +370,7 @@ class FgsManagerControllerImpl @Inject constructor(
}
private fun getNumVisibleButtonsLocked(): Int {
return runningServiceTokens.keys.count {
return runningTaskIdentifiers.keys.count {
it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
}
}
@@ -372,7 +381,7 @@ class FgsManagerControllerImpl @Inject constructor(
synchronized(lock) {
if (dialog == null) {
runningServiceTokens.keys.forEach {
runningTaskIdentifiers.keys.forEach {
it.updateUiControl()
}
@@ -434,17 +443,17 @@ class FgsManagerControllerImpl @Inject constructor(
return
}
val addedPackages = runningServiceTokens.keys.filter {
val addedPackages = runningTaskIdentifiers.keys.filter {
currentProfileIds.contains(it.userId) &&
it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
}
val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) }
val removedPackages = runningApps.keys.filter { !runningTaskIdentifiers.containsKey(it) }
addedPackages.forEach {
val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
runningApps[it] = RunningApp(
it.userId, it.packageName,
runningServiceTokens[it]!!.startTime, it.uiControl,
runningTaskIdentifiers[it]!!.startTime, it.uiControl,
packageManager.getApplicationLabel(ai),
packageManager.getUserBadgedIcon(
packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
@@ -471,7 +480,41 @@ class FgsManagerControllerImpl @Inject constructor(
private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
logEvent(stopped = true, packageName, userId, timeStarted)
activityManager.stopAppForUser(packageName, userId)
val userPackageKey = UserPackage(userId, packageName)
if (showUserVisibleJobs &&
runningTaskIdentifiers[userPackageKey]?.hasRunningJobs() == true) {
// TODO(255768978): allow fine-grained job control
jobScheduler.stopUserVisibleJobsForUser(packageName, userId)
}
if (runningTaskIdentifiers[userPackageKey]?.hasFgs() == true) {
activityManager.stopAppForUser(packageName, userId)
}
}
private fun onShowUserVisibleJobsFlagChanged() {
if (showUserVisibleJobs) {
jobScheduler.registerUserVisibleJobObserver(userVisibleJobObserver)
} else {
jobScheduler.unregisterUserVisibleJobObserver(userVisibleJobObserver)
synchronized(lock) {
for ((userPackage, startTimeAndIdentifiers) in runningTaskIdentifiers) {
if (startTimeAndIdentifiers.hasFgs()) {
// The app still has FGS running, so all we need to do is remove
// the job summaries
startTimeAndIdentifiers.clearJobSummaries()
} else {
// The app only has user-visible jobs running, so remove it from
// the map altogether
runningTaskIdentifiers.remove(userPackage)
}
}
updateNumberOfVisibleRunningPackagesLocked()
updateAppItemsLocked()
}
}
}
private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) {
@@ -564,6 +607,62 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
private inner class ForegroundServiceObserver : IForegroundServiceObserver.Stub() {
override fun onForegroundStateChanged(
token: IBinder,
packageName: String,
userId: Int,
isForeground: Boolean
) {
synchronized(lock) {
val userPackageKey = UserPackage(userId, packageName)
if (isForeground) {
runningTaskIdentifiers
.getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
.addFgsToken(token)
} else {
if (runningTaskIdentifiers[userPackageKey]?.also {
it.removeFgsToken(token)
}?.isEmpty() == true
) {
runningTaskIdentifiers.remove(userPackageKey)
}
}
updateNumberOfVisibleRunningPackagesLocked()
updateAppItemsLocked()
}
}
}
private inner class UserVisibleJobObserver : IUserVisibleJobObserver.Stub() {
override fun onUserVisibleJobStateChanged(
summary: UserVisibleJobSummary,
isRunning: Boolean
) {
synchronized(lock) {
val userPackageKey = UserPackage(summary.sourceUserId, summary.sourcePackageName)
if (isRunning) {
runningTaskIdentifiers
.getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
.addJobSummary(summary)
} else {
if (runningTaskIdentifiers[userPackageKey]?.also {
it.removeJobSummary(summary)
}?.isEmpty() == true
) {
runningTaskIdentifiers.remove(userPackageKey)
}
}
updateNumberOfVisibleRunningPackagesLocked()
updateAppItemsLocked()
}
}
}
private inner class UserPackage(
val userId: Int,
val packageName: String
@@ -630,37 +729,64 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
private data class StartTimeAndTokens(
private data class StartTimeAndIdentifiers(
val systemClock: SystemClock
) {
val startTime = systemClock.elapsedRealtime()
val tokens = mutableSetOf<IBinder>()
val fgsTokens = mutableSetOf<IBinder>()
val jobSummaries = mutableSetOf<UserVisibleJobSummary>()
fun addToken(token: IBinder) {
tokens.add(token)
fun addJobSummary(summary: UserVisibleJobSummary) {
jobSummaries.add(summary)
}
fun removeToken(token: IBinder) {
tokens.remove(token)
fun clearJobSummaries() {
jobSummaries.clear()
}
fun removeJobSummary(summary: UserVisibleJobSummary) {
jobSummaries.remove(summary)
}
fun addFgsToken(token: IBinder) {
fgsTokens.add(token)
}
fun removeFgsToken(token: IBinder) {
fgsTokens.remove(token)
}
fun hasFgs(): Boolean {
return !fgsTokens.isEmpty()
}
fun hasRunningJobs(): Boolean {
return !jobSummaries.isEmpty()
}
fun isEmpty(): Boolean {
return tokens.isEmpty()
return fgsTokens.isEmpty() && jobSummaries.isEmpty()
}
fun dump(pw: PrintWriter) {
pw.println("StartTimeAndTokens: [")
pw.println("StartTimeAndIdentifiers: [")
pw.indentIfPossible {
pw.println(
"startTime=$startTime (time running =" +
" ${systemClock.elapsedRealtime() - startTime}ms)"
)
pw.println("tokens: [")
pw.println("fgs tokens: [")
pw.indentIfPossible {
for (token in tokens) {
for (token in fgsTokens) {
pw.println("$token")
}
}
pw.println("job summaries: [")
pw.indentIfPossible {
for (summary in jobSummaries) {
pw.println("$summary")
}
}
pw.println("]")
}
pw.println("]")
@@ -724,13 +850,13 @@ class FgsManagerControllerImpl @Inject constructor(
synchronized(lock) {
pw.println("current user profiles = $currentProfileIds")
pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed")
pw.println("Running service tokens: [")
pw.println("Running task identifiers: [")
pw.indentIfPossible {
runningServiceTokens.forEach { (userPackage, startTimeAndTokens) ->
runningTaskIdentifiers.forEach { (userPackage, startTimeAndIdentifiers) ->
pw.println("{")
pw.indentIfPossible {
userPackage.dump(pw)
startTimeAndTokens.dump(pw)
startTimeAndIdentifiers.dump(pw)
}
pw.println("}")
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.qs;
import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -29,6 +30,9 @@ import static org.mockito.Mockito.verify;
import android.app.IActivityManager;
import android.app.IForegroundServiceObserver;
import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobScheduler;
import android.app.job.UserVisibleJobSummary;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -59,6 +63,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -79,6 +84,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
@Mock
IActivityManager mIActivityManager;
@Mock
JobScheduler mJobScheduler;
@Mock
PackageManager mPackageManager;
@Mock
UserTracker mUserTracker;
@@ -92,8 +99,10 @@ public class FgsManagerControllerTest extends SysuiTestCase {
private FgsManagerController mFmc;
private IForegroundServiceObserver mIForegroundServiceObserver;
private IUserVisibleJobObserver mIUserVisibleJobObserver;
private UserTracker.Callback mUserTrackerCallback;
private BroadcastReceiver mShowFgsManagerReceiver;
private InOrder mJobSchedulerInOrder;
private List<UserInfo> mUserProfiles;
@@ -111,6 +120,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mUserProfiles = new ArrayList<>();
Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles();
mJobSchedulerInOrder = Mockito.inOrder(mJobScheduler);
mFmc = createFgsManagerController();
}
@@ -131,6 +142,52 @@ public class FgsManagerControllerTest extends SysuiTestCase {
Assert.assertEquals(0, mFmc.getNumRunningPackages());
}
@Test
public void testNumPackages_jobs() throws RemoteException {
setUserProfiles(0);
setShowUserVisibleJobs(true);
UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
Assert.assertEquals(0, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
Assert.assertEquals(1, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
Assert.assertEquals(2, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, false);
Assert.assertEquals(1, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, false);
Assert.assertEquals(0, mFmc.getNumRunningPackages());
}
@Test
public void testNumPackages_FgsAndJobs() throws RemoteException {
setUserProfiles(0);
setShowUserVisibleJobs(true);
Binder b1 = new Binder();
Binder b2 = new Binder();
UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
UserVisibleJobSummary j3 = new UserVisibleJobSummary(1, 0, "pkg3", 1);
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());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
Assert.assertEquals(2, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j3, true);
Assert.assertEquals(3, mFmc.getNumRunningPackages());
mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, false);
Assert.assertEquals(2, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j3, false);
Assert.assertEquals(1, mFmc.getNumRunningPackages());
mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, false);
Assert.assertEquals(1, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, false);
Assert.assertEquals(0, mFmc.getNumRunningPackages());
}
@Test
public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException {
setUserProfiles(0);
@@ -243,6 +300,91 @@ public class FgsManagerControllerTest extends SysuiTestCase {
Assert.assertEquals(0, mFmc.visibleButtonsCount());
}
@Test
public void testShowUserVisibleJobsOnCreation() {
// Test when the default is on.
mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
"true", false);
FgsManagerController fmc = new FgsManagerControllerImpl(
mContext,
mMainExecutor,
mBackgroundExecutor,
mSystemClock,
mIActivityManager,
mJobScheduler,
mPackageManager,
mUserTracker,
mDeviceConfigProxyFake,
mDialogLaunchAnimator,
mBroadcastDispatcher,
mDumpManager
);
fmc.init();
Assert.assertTrue(fmc.getIncludesUserVisibleJobs());
ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
mJobSchedulerInOrder.verify(mJobScheduler)
.registerUserVisibleJobObserver(iUserVisibleJobObserverArgumentCaptor.capture());
Assert.assertNotNull(iUserVisibleJobObserverArgumentCaptor.getValue());
// Test when the default is off.
mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
"false", false);
fmc = new FgsManagerControllerImpl(
mContext,
mMainExecutor,
mBackgroundExecutor,
mSystemClock,
mIActivityManager,
mJobScheduler,
mPackageManager,
mUserTracker,
mDeviceConfigProxyFake,
mDialogLaunchAnimator,
mBroadcastDispatcher,
mDumpManager
);
fmc.init();
Assert.assertFalse(fmc.getIncludesUserVisibleJobs());
mJobSchedulerInOrder.verify(mJobScheduler, never()).registerUserVisibleJobObserver(any());
}
@Test
public void testShowUserVisibleJobsToggling() throws Exception {
setUserProfiles(0);
setShowUserVisibleJobs(true);
// pkg1 has only job
// pkg2 has both job and fgs
// pkg3 has only fgs
UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
Binder b2 = new Binder();
Binder b3 = new Binder();
Assert.assertEquals(0, mFmc.getNumRunningPackages());
mIForegroundServiceObserver.onForegroundStateChanged(b2, "pkg2", 0, true);
mIForegroundServiceObserver.onForegroundStateChanged(b3, "pkg3", 0, true);
Assert.assertEquals(2, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
Assert.assertEquals(3, mFmc.getNumRunningPackages());
// Turn off the flag, confirm the number of packages is updated properly.
setShowUserVisibleJobs(false);
// Only pkg1 should be removed since the other two have fgs
Assert.assertEquals(2, mFmc.getNumRunningPackages());
setShowUserVisibleJobs(true);
Assert.assertEquals(2, mFmc.getNumRunningPackages());
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j2, true);
Assert.assertEquals(3, mFmc.getNumRunningPackages());
}
private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
@@ -251,6 +393,33 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mBackgroundExecutor.runAllReady();
}
private void setShowUserVisibleJobs(boolean enable) {
if (mFmc.getIncludesUserVisibleJobs() == enable) {
// No change.
return;
}
mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
enable ? "true" : "false", false);
mBackgroundExecutor.advanceClockToLast();
mBackgroundExecutor.runAllReady();
ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
if (enable) {
mJobSchedulerInOrder.verify(mJobScheduler).registerUserVisibleJobObserver(
iUserVisibleJobObserverArgumentCaptor.capture()
);
mIUserVisibleJobObserver = iUserVisibleJobObserverArgumentCaptor.getValue();
} else {
mJobSchedulerInOrder.verify(mJobScheduler).unregisterUserVisibleJobObserver(
eq(mIUserVisibleJobObserver)
);
mIUserVisibleJobObserver = null;
}
}
private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
throws Exception {
Mockito.doReturn(uid)
@@ -275,6 +444,7 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mBackgroundExecutor,
mSystemClock,
mIActivityManager,
mJobScheduler,
mPackageManager,
mUserTracker,
mDeviceConfigProxyFake,
@@ -304,6 +474,15 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue();
mShowFgsManagerReceiver = showFgsManagerReceiverArgumentCaptor.getValue();
if (result.getIncludesUserVisibleJobs()) {
ArgumentCaptor<IUserVisibleJobObserver> iUserVisibleJobObserverArgumentCaptor =
ArgumentCaptor.forClass(IUserVisibleJobObserver.class);
mJobSchedulerInOrder.verify(mJobScheduler).registerUserVisibleJobObserver(
iUserVisibleJobObserverArgumentCaptor.capture()
);
mIUserVisibleJobObserver = iUserVisibleJobObserverArgumentCaptor.getValue();
}
return result;
}

View File

@@ -43,6 +43,9 @@ class FakeFgsManagerController(
override val showFooterDot: MutableStateFlow<Boolean> = MutableStateFlow(showFooterDot)
override var includesUserVisibleJobs = false
private set
private val numRunningPackagesListeners = LinkedHashSet<OnNumberOfPackagesChangedListener>()
private val dialogDismissedListeners = LinkedHashSet<OnDialogDismissedListener>()