Merge "Implement JS <-> TaskManager user-visible job link."

This commit is contained in:
Kweku Adams
2022-12-07 17:10:52 +00:00
committed by Android (Google) Code Review
7 changed files with 184 additions and 4 deletions

View File

@@ -158,7 +158,10 @@ public class JobSchedulerImpl extends JobScheduler {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
// TODO(255767350): implement
try {
mBinder.registerUserVisibleJobObserver(observer);
} catch (RemoteException e) {
}
}
@RequiresPermission(allOf = {
@@ -166,7 +169,10 @@ public class JobSchedulerImpl extends JobScheduler {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
// TODO(255767350): implement
try {
mBinder.unregisterUserVisibleJobObserver(observer);
} catch (RemoteException e) {
}
}
@RequiresPermission(allOf = {
@@ -174,6 +180,9 @@ public class JobSchedulerImpl extends JobScheduler {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
// TODO(255767350): implement
try {
mBinder.stopUserVisibleJobsForUser(packageName, userId);
} catch (RemoteException e) {
}
}
}

View File

@@ -16,6 +16,7 @@
package android.app.job;
import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
@@ -38,4 +39,10 @@ interface IJobScheduler {
boolean hasRunLongJobsPermission(String packageName, int userId);
List<JobInfo> getStartedJobs();
ParceledListSlice getAllJobSnapshots();
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
void registerUserVisibleJobObserver(in IUserVisibleJobObserver observer);
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
void unregisterUserVisibleJobObserver(in IUserVisibleJobObserver observer);
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
void stopUserVisibleJobsForUser(String packageName, int userId);
}

View File

@@ -98,6 +98,12 @@ public class JobParameters implements Parcelable {
*/
public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH =
JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10.
/**
* The user stopped the job via some UI (eg. Task Manager).
* @hide
*/
public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
/**
* All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -121,6 +127,7 @@ public class JobParameters implements Parcelable {
INTERNAL_STOP_REASON_DATA_CLEARED,
INTERNAL_STOP_REASON_RTC_UPDATED,
INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
INTERNAL_STOP_REASON_USER_UI_STOP,
};
/**
@@ -141,6 +148,7 @@ public class JobParameters implements Parcelable {
case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared";
case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated";
case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
default: return "unknown:" + reasonCode;
}
}
@@ -230,7 +238,7 @@ public class JobParameters implements Parcelable {
public static final int STOP_REASON_APP_STANDBY = 12;
/**
* The user stopped the job. This can happen either through force-stop, adb shell commands,
* or uninstalling.
* uninstalling, or some other UI.
*/
public static final int STOP_REASON_USER = 13;
/** The system is doing some processing that requires stopping this job. */

View File

@@ -1183,6 +1183,22 @@ class JobConcurrencyManager {
}
}
@GuardedBy("mLock")
void stopUserVisibleJobsLocked(int userId, @NonNull String packageName,
@JobParameters.StopReason int reason, int internalReasonCode) {
for (int i = mActiveServices.size() - 1; i >= 0; --i) {
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus jobStatus = jsc.getRunningJobLocked();
if (jobStatus != null && userId == jobStatus.getSourceUserId()
&& jobStatus.getSourcePackageName().equals(packageName)
&& jobStatus.isUserVisibleJob()) {
jsc.cancelExecutingJobLocked(reason, internalReasonCode,
JobParameters.getInternalReasonCodeDescription(internalReasonCode));
}
}
}
@GuardedBy("mLock")
void stopNonReadyActiveJobsLocked() {
for (int i = 0; i < mActiveServices.size(); i++) {

View File

@@ -16,11 +16,14 @@
package com.android.server.job;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -31,6 +34,7 @@ import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
import android.app.job.IUserVisibleJobObserver;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobProtoEnums;
@@ -38,6 +42,7 @@ import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
import android.app.job.UserVisibleJobSummary;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.compat.annotation.ChangeId;
@@ -68,6 +73,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -248,6 +254,8 @@ public class JobSchedulerService extends com.android.server.SystemService
static final int MSG_UID_IDLE = 7;
static final int MSG_CHECK_CHANGED_JOB_LIST = 8;
static final int MSG_CHECK_MEDIA_EXEMPTION = 9;
static final int MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS = 10;
static final int MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE = 11;
/** List of controllers that will notify this service of updates to jobs. */
final List<StateController> mControllers;
@@ -279,6 +287,9 @@ public class JobSchedulerService extends com.android.server.SystemService
@GuardedBy("mLock")
private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
new RemoteCallbackList<>();
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -1501,6 +1512,14 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
synchronized (mLock) {
mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName,
JobParameters.STOP_REASON_USER,
JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
}
}
private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
// There's no guarantee that the process has been stopped by the time we get
// here, but since this is a user-initiated action, it should be fine to just
@@ -2159,6 +2178,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
delayMillis =
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
// TODO(255767350): demote all jobs to regular for user stops so they don't keep privileges
JobStatus newJob = new JobStatus(failureToReschedule,
elapsedNowMillis + delayMillis,
JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
@@ -2509,6 +2529,52 @@ public class JobSchedulerService extends com.android.server.SystemService
args.recycle();
break;
}
case MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS: {
final IUserVisibleJobObserver observer =
(IUserVisibleJobObserver) message.obj;
synchronized (mLock) {
for (int i = mConcurrencyManager.mActiveServices.size() - 1; i >= 0;
--i) {
JobServiceContext context =
mConcurrencyManager.mActiveServices.get(i);
final JobStatus jobStatus = context.getRunningJobLocked();
if (jobStatus != null && jobStatus.isUserVisibleJob()) {
try {
observer.onUserVisibleJobStateChanged(
jobStatus.getUserVisibleJobSummary(),
/* isRunning */ true);
} catch (RemoteException e) {
// Will be unregistered automatically by
// RemoteCallbackList's dead-object tracking,
// so don't need to remove it here.
break;
}
}
}
}
break;
}
case MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE: {
final SomeArgs args = (SomeArgs) message.obj;
final JobServiceContext context = (JobServiceContext) args.arg1;
final JobStatus jobStatus = (JobStatus) args.arg2;
final UserVisibleJobSummary summary = jobStatus.getUserVisibleJobSummary();
final boolean isRunning = args.argi1 == 1;
for (int i = mUserVisibleJobObservers.beginBroadcast() - 1; i >= 0; --i) {
try {
mUserVisibleJobObservers.getBroadcastItem(i)
.onUserVisibleJobStateChanged(summary, isRunning);
} catch (RemoteException e) {
// Will be unregistered automatically by RemoteCallbackList's
// dead-object tracking, so nothing we need to do here.
}
}
mUserVisibleJobObservers.finishBroadcast();
args.recycle();
break;
}
}
maybeRunPendingJobsLocked();
}
@@ -3022,6 +3088,16 @@ public class JobSchedulerService extends com.android.server.SystemService
return adjustJobBias(bias, job);
}
void informObserversOfUserVisibleJobChange(JobServiceContext context, JobStatus jobStatus,
boolean isRunning) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = context;
args.arg2 = jobStatus;
args.argi1 = isRunning ? 1 : 0;
mHandler.obtainMessage(MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE, args)
.sendToTarget();
}
private final class BatteryStateTracker extends BroadcastReceiver {
/**
* Track whether we're "charging", where charging means that we're ready to commit to
@@ -3744,6 +3820,35 @@ public class JobSchedulerService extends com.android.server.SystemService
return new ParceledListSlice<>(snapshots);
}
}
@Override
@EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
if (observer == null) {
throw new NullPointerException("observer");
}
mUserVisibleJobObservers.register(observer);
mHandler.obtainMessage(MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS, observer)
.sendToTarget();
}
@Override
@EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
if (observer == null) {
throw new NullPointerException("observer");
}
mUserVisibleJobObservers.unregister(observer);
}
@Override
@EnforcePermission(allOf = {"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
if (packageName == null) {
throw new NullPointerException("packageName");
}
JobSchedulerService.this.stopUserVisibleJobsInternal(packageName, userId);
}
}
// Shell command infrastructure: run the given job immediately

View File

@@ -944,6 +944,9 @@ public final class JobServiceContext implements ServiceConnection {
return;
}
scheduleOpTimeOutLocked();
if (mRunningJob.isUserVisibleJob()) {
mService.informObserversOfUserVisibleJobChange(this, mRunningJob, true);
}
break;
default:
Slog.e(TAG, "Handling started job but job wasn't starting! Was "
@@ -1202,6 +1205,9 @@ public final class JobServiceContext implements ServiceConnection {
mPendingDebugStopReason = null;
mNotification = null;
removeOpTimeOutLocked();
if (completedJob.isUserVisibleJob()) {
mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
}
mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}

View File

@@ -29,11 +29,13 @@ import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTE
import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
import android.app.job.UserVisibleJobSummary;
import android.content.ClipData;
import android.content.ComponentName;
import android.net.Network;
@@ -454,6 +456,12 @@ public final class JobStatus {
*/
private boolean mExpeditedTareApproved;
/**
* Summary describing this job. Lazily created in {@link #getUserVisibleJobSummary()}
* since not every job will need it.
*/
private UserVisibleJobSummary mUserVisibleJobSummary;
/////// Booleans that track if a job is ready to run. They should be updated whenever dependent
/////// states change.
@@ -1336,6 +1344,27 @@ public final class JobStatus {
return mExpeditedQuotaApproved && mExpeditedTareApproved && isRequestedExpeditedJob();
}
/**
* Return a summary that uniquely identifies the underlying job.
*/
@NonNull
public UserVisibleJobSummary getUserVisibleJobSummary() {
if (mUserVisibleJobSummary == null) {
mUserVisibleJobSummary = new UserVisibleJobSummary(
callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
}
return mUserVisibleJobSummary;
}
/**
* @return true if this is a job whose execution should be made visible to the user.
*/
public boolean isUserVisibleJob() {
// TODO(255767350): limit to user-initiated jobs
// Placeholder implementation until we have the code in
return shouldTreatAsExpeditedJob();
}
/**
* @return true if the job is exempted from Doze restrictions and therefore allowed to run
* in Doze.