diff options
| author | 2022-12-07 17:10:52 +0000 | |
|---|---|---|
| committer | 2022-12-07 17:10:52 +0000 | |
| commit | 6ce7413f30ce896c76b79fc42a4307dcff2863f0 (patch) | |
| tree | dfd82f69ef5d437531df86d0dc7db0b86d620537 | |
| parent | 48740cd2f16be1142e04c584fdd7d311d6ef898e (diff) | |
| parent | b3feb9d621e7bf87ef7d25fe0b5fb9ed5281ee3b (diff) | |
Merge "Implement JS <-> TaskManager user-visible job link."
7 files changed, 184 insertions, 4 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index 2d3201aab133..4242cf843874 100644 --- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java +++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java @@ -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) { + } } } diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl index bf29dc98887a..c87a2aff7dde 100644 --- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl +++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl @@ -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); } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index ed72530d8608..0205430e707f 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -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. */ diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 47f68902e88f..16201b29571e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1184,6 +1184,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++) { JobServiceContext serviceContext = mActiveServices.get(i); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 9fb2af736cea..ad6eff07b977 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -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 diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index b20eedca776f..fead68e021db 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -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); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index af8e7272605b..83b6a8ea3916 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -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. @@ -1337,6 +1345,27 @@ public final class JobStatus { } /** + * 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. */ |