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
This commit is contained in:
Sergey Nikolaienkov
2019-08-26 13:13:08 +02:00
parent 6f9af93503
commit 2ad8af146c
4 changed files with 250 additions and 52 deletions

View File

@@ -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<JobRestriction> 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;