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; package android.app;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.job.IJobScheduler; import android.app.job.IJobScheduler;
import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo; import android.app.job.JobInfo;
import android.app.job.JobScheduler; import android.app.job.JobScheduler;
import android.app.job.JobSnapshot; import android.app.job.JobSnapshot;
@@ -119,4 +122,28 @@ public class JobSchedulerImpl extends JobScheduler {
return null; 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") @SuppressWarnings("HiddenAbstractMethod")
public abstract List<JobSnapshot> getAllJobSnapshots(); 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 = public static final String TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS =
"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. * (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.UiModeManager;
import android.app.WallpaperManager; import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.job.JobScheduler;
import android.app.role.RoleManager; import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager; import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager; import android.app.trust.TrustManager;
@@ -277,6 +278,12 @@ public class FrameworkServicesModule {
context.getSystemService(BiometricManager.class); context.getSystemService(BiometricManager.class);
} }
@Provides
@Singleton
static JobScheduler provideJobScheduler(Context context) {
return context.getSystemService(JobScheduler.class);
}
@Provides @Provides
@Singleton @Singleton
static InteractionJankMonitor provideInteractionJankMonitor() { static InteractionJankMonitor provideInteractionJankMonitor() {

View File

@@ -18,6 +18,9 @@ package com.android.systemui.qs
import android.app.IActivityManager import android.app.IActivityManager
import android.app.IForegroundServiceObserver 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.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent 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_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT 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_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.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable import com.android.systemui.Dumpable
import com.android.systemui.R import com.android.systemui.R
@@ -92,6 +96,8 @@ interface FgsManagerController {
*/ */
val showFooterDot: StateFlow<Boolean> val showFooterDot: StateFlow<Boolean>
val includesUserVisibleJobs: Boolean
/** /**
* Initialize this controller. This should be called once, before this controller is used for * Initialize this controller. This should be called once, before this controller is used for
* the first time. * the first time.
@@ -141,19 +147,21 @@ class FgsManagerControllerImpl @Inject constructor(
@Background private val backgroundExecutor: Executor, @Background private val backgroundExecutor: Executor,
private val systemClock: SystemClock, private val systemClock: SystemClock,
private val activityManager: IActivityManager, private val activityManager: IActivityManager,
private val jobScheduler: JobScheduler,
private val packageManager: PackageManager, private val packageManager: PackageManager,
private val userTracker: UserTracker, 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,
private val dumpManager: DumpManager private val dumpManager: DumpManager
) : IForegroundServiceObserver.Stub(), Dumpable, FgsManagerController { ) : Dumpable, FgsManagerController {
companion object { companion object {
private const val INTERACTION_JANK_TAG = "active_background_apps" private const val INTERACTION_JANK_TAG = "active_background_apps"
private const val DEFAULT_TASK_MANAGER_ENABLED = true 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_FOOTER_DOT = false
private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true 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 override var newChangesSinceDialogWasDismissed = false
@@ -167,6 +175,11 @@ class FgsManagerControllerImpl @Inject constructor(
private var showStopBtnForUserAllowlistedApps = false private var showStopBtnForUserAllowlistedApps = false
private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
override val includesUserVisibleJobs: Boolean
get() = showUserVisibleJobs
override val numRunningPackages: Int override val numRunningPackages: Int
get() { get() {
synchronized(lock) { synchronized(lock) {
@@ -186,7 +199,7 @@ class FgsManagerControllerImpl @Inject constructor(
private var currentProfileIds = mutableSetOf<Int>() private var currentProfileIds = mutableSetOf<Int>()
@GuardedBy("lock") @GuardedBy("lock")
private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>() private val runningTaskIdentifiers = mutableMapOf<UserPackage, StartTimeAndIdentifiers>()
@GuardedBy("lock") @GuardedBy("lock")
private var dialog: SystemUIDialog? = null private var dialog: SystemUIDialog? = null
@@ -210,13 +223,29 @@ class FgsManagerControllerImpl @Inject constructor(
} }
} }
private val foregroundServiceObserver = ForegroundServiceObserver()
private val userVisibleJobObserver = UserVisibleJobObserver()
override fun init() { override fun init() {
synchronized(lock) { synchronized(lock) {
if (initialized) { if (initialized) {
return return
} }
showUserVisibleJobs = deviceConfigProxy.getBoolean(
NAMESPACE_SYSTEMUI,
TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
try { 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) { } catch (e: RemoteException) {
e.rethrowFromSystemServer() e.rethrowFromSystemServer()
} }
@@ -235,6 +264,12 @@ class FgsManagerControllerImpl @Inject constructor(
showStopBtnForUserAllowlistedApps = it.getBoolean( showStopBtnForUserAllowlistedApps = it.getBoolean(
TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
showStopBtnForUserAllowlistedApps) showStopBtnForUserAllowlistedApps)
var wasShowingUserVisibleJobs = showUserVisibleJobs
showUserVisibleJobs = it.getBoolean(
TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
onShowUserVisibleJobsFlagChanged()
}
} }
_isAvailable.value = deviceConfigProxy.getBoolean( _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") @GuardedBy("lock")
private val onNumberOfPackagesChangedListeners = private val onNumberOfPackagesChangedListeners =
mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>() mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>()
@@ -336,7 +345,7 @@ class FgsManagerControllerImpl @Inject constructor(
} }
private fun getNumVisiblePackagesLocked(): Int { private fun getNumVisiblePackagesLocked(): Int {
return runningServiceTokens.keys.count { return runningTaskIdentifiers.keys.count {
it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId) it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
} }
} }
@@ -361,7 +370,7 @@ class FgsManagerControllerImpl @Inject constructor(
} }
private fun getNumVisibleButtonsLocked(): Int { private fun getNumVisibleButtonsLocked(): Int {
return runningServiceTokens.keys.count { return runningTaskIdentifiers.keys.count {
it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId) it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
} }
} }
@@ -372,7 +381,7 @@ class FgsManagerControllerImpl @Inject constructor(
synchronized(lock) { synchronized(lock) {
if (dialog == null) { if (dialog == null) {
runningServiceTokens.keys.forEach { runningTaskIdentifiers.keys.forEach {
it.updateUiControl() it.updateUiControl()
} }
@@ -434,17 +443,17 @@ class FgsManagerControllerImpl @Inject constructor(
return return
} }
val addedPackages = runningServiceTokens.keys.filter { val addedPackages = runningTaskIdentifiers.keys.filter {
currentProfileIds.contains(it.userId) && currentProfileIds.contains(it.userId) &&
it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true 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 { addedPackages.forEach {
val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId) val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
runningApps[it] = RunningApp( runningApps[it] = RunningApp(
it.userId, it.packageName, it.userId, it.packageName,
runningServiceTokens[it]!!.startTime, it.uiControl, runningTaskIdentifiers[it]!!.startTime, it.uiControl,
packageManager.getApplicationLabel(ai), packageManager.getApplicationLabel(ai),
packageManager.getUserBadgedIcon( packageManager.getUserBadgedIcon(
packageManager.getApplicationIcon(ai), UserHandle.of(it.userId) packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
@@ -471,7 +480,41 @@ class FgsManagerControllerImpl @Inject constructor(
private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) { private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
logEvent(stopped = true, packageName, userId, timeStarted) 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) { 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( private inner class UserPackage(
val userId: Int, val userId: Int,
val packageName: String val packageName: String
@@ -630,37 +729,64 @@ class FgsManagerControllerImpl @Inject constructor(
} }
} }
private data class StartTimeAndTokens( private data class StartTimeAndIdentifiers(
val systemClock: SystemClock val systemClock: SystemClock
) { ) {
val startTime = systemClock.elapsedRealtime() val startTime = systemClock.elapsedRealtime()
val tokens = mutableSetOf<IBinder>() val fgsTokens = mutableSetOf<IBinder>()
val jobSummaries = mutableSetOf<UserVisibleJobSummary>()
fun addToken(token: IBinder) { fun addJobSummary(summary: UserVisibleJobSummary) {
tokens.add(token) jobSummaries.add(summary)
} }
fun removeToken(token: IBinder) { fun clearJobSummaries() {
tokens.remove(token) 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 { fun isEmpty(): Boolean {
return tokens.isEmpty() return fgsTokens.isEmpty() && jobSummaries.isEmpty()
} }
fun dump(pw: PrintWriter) { fun dump(pw: PrintWriter) {
pw.println("StartTimeAndTokens: [") pw.println("StartTimeAndIdentifiers: [")
pw.indentIfPossible { pw.indentIfPossible {
pw.println( pw.println(
"startTime=$startTime (time running =" + "startTime=$startTime (time running =" +
" ${systemClock.elapsedRealtime() - startTime}ms)" " ${systemClock.elapsedRealtime() - startTime}ms)"
) )
pw.println("tokens: [") pw.println("fgs tokens: [")
pw.indentIfPossible { pw.indentIfPossible {
for (token in tokens) { for (token in fgsTokens) {
pw.println("$token") pw.println("$token")
} }
} }
pw.println("job summaries: [")
pw.indentIfPossible {
for (summary in jobSummaries) {
pw.println("$summary")
}
}
pw.println("]") pw.println("]")
} }
pw.println("]") pw.println("]")
@@ -724,13 +850,13 @@ class FgsManagerControllerImpl @Inject constructor(
synchronized(lock) { synchronized(lock) {
pw.println("current user profiles = $currentProfileIds") pw.println("current user profiles = $currentProfileIds")
pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed") pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed")
pw.println("Running service tokens: [") pw.println("Running task identifiers: [")
pw.indentIfPossible { pw.indentIfPossible {
runningServiceTokens.forEach { (userPackage, startTimeAndTokens) -> runningTaskIdentifiers.forEach { (userPackage, startTimeAndIdentifiers) ->
pw.println("{") pw.println("{")
pw.indentIfPossible { pw.indentIfPossible {
userPackage.dump(pw) userPackage.dump(pw)
startTimeAndTokens.dump(pw) startTimeAndIdentifiers.dump(pw)
} }
pw.println("}") pw.println("}")
} }

View File

@@ -18,6 +18,7 @@ package com.android.systemui.qs;
import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE; 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.anyInt;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@@ -29,6 +30,9 @@ import static org.mockito.Mockito.verify;
import android.app.IActivityManager; import android.app.IActivityManager;
import android.app.IForegroundServiceObserver; 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.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -59,6 +63,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers; import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@@ -79,6 +84,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
@Mock @Mock
IActivityManager mIActivityManager; IActivityManager mIActivityManager;
@Mock @Mock
JobScheduler mJobScheduler;
@Mock
PackageManager mPackageManager; PackageManager mPackageManager;
@Mock @Mock
UserTracker mUserTracker; UserTracker mUserTracker;
@@ -92,8 +99,10 @@ public class FgsManagerControllerTest extends SysuiTestCase {
private FgsManagerController mFmc; private FgsManagerController mFmc;
private IForegroundServiceObserver mIForegroundServiceObserver; private IForegroundServiceObserver mIForegroundServiceObserver;
private IUserVisibleJobObserver mIUserVisibleJobObserver;
private UserTracker.Callback mUserTrackerCallback; private UserTracker.Callback mUserTrackerCallback;
private BroadcastReceiver mShowFgsManagerReceiver; private BroadcastReceiver mShowFgsManagerReceiver;
private InOrder mJobSchedulerInOrder;
private List<UserInfo> mUserProfiles; private List<UserInfo> mUserProfiles;
@@ -111,6 +120,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mUserProfiles = new ArrayList<>(); mUserProfiles = new ArrayList<>();
Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles(); Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles();
mJobSchedulerInOrder = Mockito.inOrder(mJobScheduler);
mFmc = createFgsManagerController(); mFmc = createFgsManagerController();
} }
@@ -131,6 +142,52 @@ public class FgsManagerControllerTest extends SysuiTestCase {
Assert.assertEquals(0, mFmc.getNumRunningPackages()); 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 @Test
public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException { public void testNumPackagesDoesNotChangeWhenSecondFgsIsStarted() throws RemoteException {
setUserProfiles(0); setUserProfiles(0);
@@ -243,6 +300,91 @@ public class FgsManagerControllerTest extends SysuiTestCase {
Assert.assertEquals(0, mFmc.visibleButtonsCount()); 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) { private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS, SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
@@ -251,6 +393,33 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mBackgroundExecutor.runAllReady(); 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) private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
throws Exception { throws Exception {
Mockito.doReturn(uid) Mockito.doReturn(uid)
@@ -275,6 +444,7 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mBackgroundExecutor, mBackgroundExecutor,
mSystemClock, mSystemClock,
mIActivityManager, mIActivityManager,
mJobScheduler,
mPackageManager, mPackageManager,
mUserTracker, mUserTracker,
mDeviceConfigProxyFake, mDeviceConfigProxyFake,
@@ -304,6 +474,15 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue(); mUserTrackerCallback = userTrackerCallbackArgumentCaptor.getValue();
mShowFgsManagerReceiver = showFgsManagerReceiverArgumentCaptor.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; return result;
} }

View File

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