From 2ad8af146c2357ef1fe3a765124acdd33339c245 Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Mon, 26 Aug 2019 13:13:08 +0200 Subject: [PATCH] Introduce JobRestrictions for JobSchedulerService Introducing a JobRestriction class - an abstraction for imposing additional restrictions regarding whether JobSchedulerService jobs should schedule a particlular job based on the state of the system/device. Bug: 133217178 Change-Id: I497ada3708ca1bb20d1acf4ec674cc5e0e0f201c --- .../server/job/JobSchedulerService.java | 134 +++++++++++------- .../job/restrictions/JobRestriction.java | 72 ++++++++++ .../ThermalStatusRestriction.java | 81 +++++++++++ core/proto/android/server/jobscheduler.proto | 15 +- 4 files changed, 250 insertions(+), 52 deletions(-) create mode 100644 apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java create mode 100644 apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java 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 a633350996cda..329d4b7c8f1e8 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -56,8 +56,6 @@ import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Handler; -import android.os.IThermalService; -import android.os.IThermalStatusListener; import android.os.Looper; import android.os.Message; import android.os.Process; @@ -66,7 +64,6 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.SystemClock; -import android.os.Temperature; import android.os.UserHandle; import android.os.UserManagerInternal; import android.os.WorkSource; @@ -81,7 +78,6 @@ import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.ArrayUtils; @@ -105,6 +101,8 @@ import com.android.server.job.controllers.QuotaController; import com.android.server.job.controllers.StateController; import com.android.server.job.controllers.StorageController; import com.android.server.job.controllers.TimeController; +import com.android.server.job.restrictions.JobRestriction; +import com.android.server.job.restrictions.ThermalStatusRestriction; import libcore.util.EmptyArray; @@ -186,12 +184,12 @@ public class JobSchedulerService extends com.android.server.SystemService private final DeviceIdleJobsController mDeviceIdleJobsController; /** Needed to get remaining quota time. */ private final QuotaController mQuotaController; - - /** Need directly for receiving thermal events */ - private IThermalService mThermalService; - /** Thermal constraint. */ - @GuardedBy("mLock") - private boolean mThermalConstraint = false; + /** + * List of restrictions. + * Note: do not add to or remove from this list at runtime except in the constructor, because we + * do not synchronize access to this list. + */ + private final List mJobRestrictions; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list @@ -285,19 +283,6 @@ public class JobSchedulerService extends com.android.server.SystemService } } - /** - * Thermal event received from Thermal Service - */ - private final class ThermalStatusListener extends IThermalStatusListener.Stub { - @Override public void onStatusChange(int status) { - // Throttle for Temperature.THROTTLING_SEVERE and above - synchronized (mLock) { - mThermalConstraint = status >= Temperature.THROTTLING_SEVERE; - } - onControllerStateChanged(); - } - } - static class MaxJobCounts { private final KeyValueListParser.IntValue mTotal; private final KeyValueListParser.IntValue mMaxBg; @@ -1292,6 +1277,10 @@ public class JobSchedulerService extends com.android.server.SystemService mQuotaController = new QuotaController(this); mControllers.add(mQuotaController); + // Create restrictions + mJobRestrictions = new ArrayList<>(); + mJobRestrictions.add(new ThermalStatusRestriction(this)); + // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. if (!mJobs.jobTimesInflatedValid()) { @@ -1383,15 +1372,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Remove any jobs that are not associated with any of the current users. cancelJobsForNonExistentUsers(); - // Register thermal callback - mThermalService = IThermalService.Stub.asInterface( - ServiceManager.getService(Context.THERMAL_SERVICE)); - if (mThermalService != null) { - try { - mThermalService.registerThermalStatusListener(new ThermalStatusListener()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to register thermal callback.", e); - } + + for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { + mJobRestrictions.get(i).onSystemServicesReady(); } } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mLock) { @@ -1833,9 +1816,28 @@ public class JobSchedulerService extends com.android.server.SystemService } } - private boolean isJobThermalConstrainedLocked(JobStatus job) { - return mThermalConstraint && job.hasConnectivityConstraint() - && (evaluateJobPriorityLocked(job) < JobInfo.PRIORITY_FOREGROUND_APP); + /** + * Check if a job is restricted by any of the declared {@link JobRestriction}s. + * Note, that the jobs with {@link JobInfo#PRIORITY_FOREGROUND_APP} priority or higher may not + * be restricted, thus we won't even perform the check, but simply return null early. + * + * @param job to be checked + * @return the first {@link JobRestriction} restricting the given job that has been found; null + * - if passes all the restrictions or has priority {@link JobInfo#PRIORITY_FOREGROUND_APP} + * or higher. + */ + private JobRestriction checkIfRestricted(JobStatus job) { + if (evaluateJobPriorityLocked(job) >= JobInfo.PRIORITY_FOREGROUND_APP) { + // Jobs with PRIORITY_FOREGROUND_APP or higher should not be restricted + return null; + } + for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { + final JobRestriction restriction = mJobRestrictions.get(i); + if (restriction.isJobRestricted(job)) { + return restriction; + } + } + return null; } private void stopNonReadyActiveJobsLocked() { @@ -1849,10 +1851,13 @@ public class JobSchedulerService extends com.android.server.SystemService serviceContext.cancelExecutingJobLocked( JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, "cancelled due to unsatisfied constraints"); - } else if (isJobThermalConstrainedLocked(running)) { - serviceContext.cancelExecutingJobLocked( - JobParameters.REASON_DEVICE_THERMAL, - "cancelled due to thermal condition"); + } else { + final JobRestriction restriction = checkIfRestricted(running); + if (restriction != null) { + final int reason = restriction.getReason(); + serviceContext.cancelExecutingJobLocked(reason, + "restricted due to " + JobParameters.getReasonName(reason)); + } } } } @@ -2089,7 +2094,7 @@ public class JobSchedulerService extends com.android.server.SystemService return false; } - if (isJobThermalConstrainedLocked(job)) { + if (checkIfRestricted(job) != null) { return false; } @@ -2170,7 +2175,7 @@ public class JobSchedulerService extends com.android.server.SystemService return false; } - if (isJobThermalConstrainedLocked(job)) { + if (checkIfRestricted(job) != null) { return false; } @@ -2982,9 +2987,12 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(" In parole?: "); pw.print(mInParole); pw.println(); - pw.print(" In thermal throttling?: "); - pw.print(mThermalConstraint); - pw.println(); + + for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { + pw.print(" "); + mJobRestrictions.get(i).dumpConstants(pw); + pw.println(); + } pw.println(); pw.println("Started users: " + Arrays.toString(mStartedUsers)); @@ -3005,14 +3013,30 @@ public class JobSchedulerService extends com.android.server.SystemService job.dump(pw, " ", true, nowElapsed); + + pw.print(" Restricted due to:"); + final boolean isRestricted = checkIfRestricted(job) != null; + if (isRestricted) { + for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { + final JobRestriction restriction = mJobRestrictions.get(i); + if (restriction.isJobRestricted(job)) { + final int reason = restriction.getReason(); + pw.write(" " + JobParameters.getReasonName(reason) + "[" + reason + "]"); + } + } + } else { + pw.print(" none"); + } + pw.println("."); + pw.print(" Ready: "); pw.print(isReadyToBeExecutedLocked(job)); pw.print(" (job="); pw.print(job.isReady()); pw.print(" user="); pw.print(areUsersStartedLocked(job)); - pw.print(" !thermal="); - pw.print(!isJobThermalConstrainedLocked(job)); + pw.print(" !restricted="); + pw.print(!isRestricted); pw.print(" !pending="); pw.print(!mPendingJobs.contains(job)); pw.print(" !active="); @@ -3152,7 +3176,9 @@ public class JobSchedulerService extends com.android.server.SystemService proto.end(settingsToken); proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole); - proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint); + for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { + mJobRestrictions.get(i).dumpConstants(proto); + } for (int u : mStartedUsers) { proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u); @@ -3179,8 +3205,8 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(JobSchedulerServiceDumpProto.RegisteredJob.ARE_USERS_STARTED, areUsersStartedLocked(job)); proto.write( - JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_THERMAL_CONSTRAINED, - isJobThermalConstrainedLocked(job)); + JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_RESTRICTED, + checkIfRestricted(job) != null); proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING, mPendingJobs.contains(job)); proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE, @@ -3190,6 +3216,16 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_USABLE, isComponentUsable(job)); + for (JobRestriction restriction : mJobRestrictions) { + final long restrictionsToken = proto.start( + JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS); + proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON, + restriction.getReason()); + proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING, + restriction.isJobRestricted(job)); + proto.end(restrictionsToken); + } + proto.end(rjToken); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java new file mode 100644 index 0000000000000..e180c55e1bf23 --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 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 com.android.server.job.restrictions; + +import android.app.job.JobInfo; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.controllers.JobStatus; + +/** + * Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs + * should be scheduled or not based on the state of the system/device. + * Every restriction is associated with exactly one reason (from {@link + * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link + * #getReason()}. + * Note, that this is not taken into account for the jobs that have priority + * {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher. + */ +public abstract class JobRestriction { + + final JobSchedulerService mService; + private final int mReason; + + JobRestriction(JobSchedulerService service, int reason) { + mService = service; + mReason = reason; + } + + /** + * Called when the system boot phase has reached + * {@link com.android.server.SystemService#PHASE_SYSTEM_SERVICES_READY}. + */ + public void onSystemServicesReady() { + } + + /** + * Called by {@link JobSchedulerService} to check if it may proceed with scheduling the job (in + * case all constraints are satisfied and all other {@link JobRestriction}s are fine with it) + * + * @param job to be checked + * @return false if the {@link JobSchedulerService} should not schedule this job at the moment, + * true - otherwise + */ + public abstract boolean isJobRestricted(JobStatus job); + + /** Dump any internal constants the Restriction may have. */ + public abstract void dumpConstants(IndentingPrintWriter pw); + + /** Dump any internal constants the Restriction may have. */ + public abstract void dumpConstants(ProtoOutputStream proto); + + /** @return reason code for the Restriction. */ + public final int getReason() { + return mReason; + } +} diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java new file mode 100644 index 0000000000000..b97da59f8d177 --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 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 com.android.server.job.restrictions; + +import android.app.job.JobParameters; +import android.content.Context; +import android.os.IThermalService; +import android.os.IThermalStatusListener; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.Temperature; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.JobSchedulerServiceDumpProto; +import com.android.server.job.controllers.JobStatus; + +public class ThermalStatusRestriction extends JobRestriction { + private static final String TAG = "ThermalStatusRestriction"; + + private volatile boolean mIsThermalRestricted = false; + + public ThermalStatusRestriction(JobSchedulerService service) { + super(service, JobParameters.REASON_DEVICE_THERMAL); + } + + @Override + public void onSystemServicesReady() { + final IThermalService thermalService = IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + if (thermalService != null) { + try { + thermalService.registerThermalStatusListener(new IThermalStatusListener.Stub() { + @Override + public void onStatusChange(int status) { + final boolean shouldBeActive = status >= Temperature.THROTTLING_SEVERE; + if (mIsThermalRestricted == shouldBeActive) { + return; + } + mIsThermalRestricted = shouldBeActive; + mService.onControllerStateChanged(); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register thermal callback.", e); + } + } + } + + @Override + public boolean isJobRestricted(JobStatus job) { + return mIsThermalRestricted && job.hasConnectivityConstraint(); + } + + @Override + public void dumpConstants(IndentingPrintWriter pw) { + pw.print("In thermal throttling?: "); + pw.print(mIsThermalRestricted); + } + + @Override + public void dumpConstants(ProtoOutputStream proto) { + proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mIsThermalRestricted); + } +} diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 79167ab476c11..15b98af8e1f38 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -48,6 +48,13 @@ message JobSchedulerServiceDumpProto { repeated int32 started_users = 2; + message JobRestriction { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional .android.app.job.StopReasonEnum reason = 1; + optional bool is_restricting = 2; + } + message RegisteredJob { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -56,20 +63,22 @@ message JobSchedulerServiceDumpProto { optional bool is_job_ready_to_be_executed = 10; // A job is ready to be executed if: - // is_job_ready && are_users_started && !is_job_thermal_constrained && !is_job_pending && + // is_job_ready && are_users_started && !is_job_restricted && !is_job_pending && // !is_job_currently_active && !is_uid_backing_up && // is_component_usable. optional bool is_job_ready = 3; optional bool are_users_started = 4; - optional bool is_job_thermal_constrained = 11; + optional bool is_job_restricted = 11; optional bool is_job_pending = 5; optional bool is_job_currently_active = 6; optional bool is_uid_backing_up = 7; optional bool is_component_usable = 8; + repeated JobRestriction restrictions = 12; + reserved 9; // last_run_heartbeat - // Next tag: 12 + // Next tag: 13 } repeated RegisteredJob registered_jobs = 3;