Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2ad8af14 authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov
Browse files

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
parent 6f9af935
Loading
Loading
Loading
Loading
+85 −49
Original line number Diff line number Diff line
@@ -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);

            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);
                }
            }
+72 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+81 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+12 −3
Original line number Diff line number Diff line
@@ -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;