Merge "Add logic to show apps running certain jobs in Task Manager." into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
816123320d
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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("}")
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user