Add logic to show apps running certain jobs in Task Manager.

For now, we will show apps with an active FGS or jobs that the user
should be aware of (ie. "user-visible jobs") in the exact same way.
Users will be able to stop the ongoing FGS and user-visible jobs
using the same button. This won't be used to show apps on TM, so
the JobScheduler implementation is empty.
We'll add the JobScheduler implementation in U.

Bug: 255767350
Change-Id: Iaf482926c1f568aee26a2798ce827c6138f3f779
Test: atest SystemUITests
Test: Manually start jobs and interact with the Task Manager
This commit is contained in:
Kweku Adams
2022-10-26 16:56:30 +00:00
parent 793936a15b
commit 4167f0c3d6
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

@@ -30,6 +30,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;
@@ -273,6 +274,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>()