diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
commit | 10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch) | |
tree | 8dbd149eb350320a29c3d10e7ad3201de1c5cbee /com/android/server/job/controllers/TimeController.java | |
parent | 677516fb6b6f207d373984757d3d9450474b6b00 (diff) | |
download | android-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz |
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \
--bid 4335822 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4335822.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'com/android/server/job/controllers/TimeController.java')
-rw-r--r-- | com/android/server/job/controllers/TimeController.java | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/com/android/server/job/controllers/TimeController.java b/com/android/server/job/controllers/TimeController.java new file mode 100644 index 00000000..d90699a6 --- /dev/null +++ b/com/android/server/job/controllers/TimeController.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2014 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.controllers; + +import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; +import android.content.Context; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.WorkSource; +import android.util.Slog; +import android.util.TimeUtils; + +import com.android.server.job.JobSchedulerService; +import com.android.server.job.StateChangedListener; + +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** + * This class sets an alarm for the next expiring job, and determines whether a job's minimum + * delay has been satisfied. + */ +public final class TimeController extends StateController { + private static final String TAG = "JobScheduler.Time"; + + /** Deadline alarm tag for logging purposes */ + private final String DEADLINE_TAG = "*job.deadline*"; + /** Delay alarm tag for logging purposes */ + private final String DELAY_TAG = "*job.delay*"; + + private long mNextJobExpiredElapsedMillis; + private long mNextDelayExpiredElapsedMillis; + + private AlarmManager mAlarmService = null; + /** List of tracked jobs, sorted asc. by deadline */ + private final List<JobStatus> mTrackedJobs = new LinkedList<>(); + /** Singleton. */ + private static TimeController mSingleton; + + public static synchronized TimeController get(JobSchedulerService jms) { + if (mSingleton == null) { + mSingleton = new TimeController(jms, jms.getContext(), jms.getLock()); + } + return mSingleton; + } + + private TimeController(StateChangedListener stateChangedListener, Context context, + Object lock) { + super(stateChangedListener, context, lock); + + mNextJobExpiredElapsedMillis = Long.MAX_VALUE; + mNextDelayExpiredElapsedMillis = Long.MAX_VALUE; + } + + /** + * Check if the job has a timing constraint, and if so determine where to insert it in our + * list. + */ + @Override + public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) { + if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) { + maybeStopTrackingJobLocked(job, null, false); + + // First: check the constraints now, because if they are already satisfied + // then there is no need to track it. This gives us a fast path for a common + // pattern of having a job with a 0 deadline constraint ("run immediately"). + // Unlike most controllers, once one of our constraints has been satisfied, it + // will never be unsatisfied (our time base can not go backwards). + final long nowElapsedMillis = SystemClock.elapsedRealtime(); + if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) { + return; + } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job, + nowElapsedMillis)) { + return; + } + + boolean isInsert = false; + ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size()); + while (it.hasPrevious()) { + JobStatus ts = it.previous(); + if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) { + // Insert + isInsert = true; + break; + } + } + if (isInsert) { + it.next(); + } + it.add(job); + job.setTrackingController(JobStatus.TRACKING_TIME); + maybeUpdateAlarmsLocked( + job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE, + job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE, + job.getSourceUid()); + } + } + + /** + * When we stop tracking a job, we only need to update our alarms if the job we're no longer + * tracking was the one our alarms were based off of. + */ + @Override + public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob, + boolean forUpdate) { + if (job.clearTrackingController(JobStatus.TRACKING_TIME)) { + if (mTrackedJobs.remove(job)) { + checkExpiredDelaysAndResetAlarm(); + checkExpiredDeadlinesAndResetAlarm(); + } + } + } + + /** + * Determines whether this controller can stop tracking the given job. + * The controller is no longer interested in a job once its time constraint is satisfied, and + * the job's deadline is fulfilled - unlike other controllers a time constraint can't toggle + * back and forth. + */ + private boolean canStopTrackingJobLocked(JobStatus job) { + return (!job.hasTimingDelayConstraint() || + (job.satisfiedConstraints&JobStatus.CONSTRAINT_TIMING_DELAY) != 0) && + (!job.hasDeadlineConstraint() || + (job.satisfiedConstraints&JobStatus.CONSTRAINT_DEADLINE) != 0); + } + + private void ensureAlarmServiceLocked() { + if (mAlarmService == null) { + mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + } + } + + /** + * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler + * if so, removing them from this list, and updating the alarm for the next expiry time. + */ + private void checkExpiredDeadlinesAndResetAlarm() { + synchronized (mLock) { + long nextExpiryTime = Long.MAX_VALUE; + int nextExpiryUid = 0; + final long nowElapsedMillis = SystemClock.elapsedRealtime(); + + Iterator<JobStatus> it = mTrackedJobs.iterator(); + while (it.hasNext()) { + JobStatus job = it.next(); + if (!job.hasDeadlineConstraint()) { + continue; + } + + if (evaluateDeadlineConstraint(job, nowElapsedMillis)) { + mStateChangedListener.onRunJobNow(job); + it.remove(); + } else { // Sorted by expiry time, so take the next one and stop. + nextExpiryTime = job.getLatestRunTimeElapsed(); + nextExpiryUid = job.getSourceUid(); + break; + } + } + setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid); + } + } + + private boolean evaluateDeadlineConstraint(JobStatus job, long nowElapsedMillis) { + final long jobDeadline = job.getLatestRunTimeElapsed(); + + if (jobDeadline <= nowElapsedMillis) { + if (job.hasTimingDelayConstraint()) { + job.setTimingDelayConstraintSatisfied(true); + } + job.setDeadlineConstraintSatisfied(true); + return true; + } + return false; + } + + /** + * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of + * tracked jobs and marks them as ready as appropriate. + */ + private void checkExpiredDelaysAndResetAlarm() { + synchronized (mLock) { + final long nowElapsedMillis = SystemClock.elapsedRealtime(); + long nextDelayTime = Long.MAX_VALUE; + int nextDelayUid = 0; + boolean ready = false; + Iterator<JobStatus> it = mTrackedJobs.iterator(); + while (it.hasNext()) { + final JobStatus job = it.next(); + if (!job.hasTimingDelayConstraint()) { + continue; + } + if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) { + if (canStopTrackingJobLocked(job)) { + it.remove(); + } + if (job.isReady()) { + ready = true; + } + } else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) { + // If this job still doesn't have its delay constraint satisfied, + // then see if it is the next upcoming delay time for the alarm. + final long jobDelayTime = job.getEarliestRunTime(); + if (nextDelayTime > jobDelayTime) { + nextDelayTime = jobDelayTime; + nextDelayUid = job.getSourceUid(); + } + } + } + if (ready) { + mStateChangedListener.onControllerStateChanged(); + } + setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid); + } + } + + private boolean evaluateTimingDelayConstraint(JobStatus job, long nowElapsedMillis) { + final long jobDelayTime = job.getEarliestRunTime(); + if (jobDelayTime <= nowElapsedMillis) { + job.setTimingDelayConstraintSatisfied(true); + return true; + } + return false; + } + + private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed, + int uid) { + if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) { + setDelayExpiredAlarmLocked(delayExpiredElapsed, uid); + } + if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) { + setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid); + } + } + + /** + * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's + * delay will expire. + * This alarm <b>will</b> wake up the phone. + */ + private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) { + alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); + mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis; + updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener, + mNextDelayExpiredElapsedMillis, uid); + } + + /** + * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's + * deadline will expire. + * This alarm <b>will</b> wake up the phone. + */ + private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) { + alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); + mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis; + updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener, + mNextJobExpiredElapsedMillis, uid); + } + + private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) { + final long earliestWakeupTimeElapsed = SystemClock.elapsedRealtime(); + if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) { + return earliestWakeupTimeElapsed; + } + return proposedAlarmTimeElapsedMillis; + } + + private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener, + long alarmTimeElapsed, int uid) { + ensureAlarmServiceLocked(); + if (alarmTimeElapsed == Long.MAX_VALUE) { + mAlarmService.cancel(listener); + } else { + if (DEBUG) { + Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed); + } + mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed, + AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid)); + } + } + + // Job/delay expiration alarm handling + + private final OnAlarmListener mDeadlineExpiredListener = new OnAlarmListener() { + @Override + public void onAlarm() { + if (DEBUG) { + Slog.d(TAG, "Deadline-expired alarm fired"); + } + checkExpiredDeadlinesAndResetAlarm(); + } + }; + + private final OnAlarmListener mNextDelayExpiredListener = new OnAlarmListener() { + @Override + public void onAlarm() { + if (DEBUG) { + Slog.d(TAG, "Delay-expired alarm fired"); + } + checkExpiredDelaysAndResetAlarm(); + } + }; + + @Override + public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { + final long nowElapsed = SystemClock.elapsedRealtime(); + pw.print("Alarms: now="); + pw.print(SystemClock.elapsedRealtime()); + pw.println(); + pw.print("Next delay alarm in "); + TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw); + pw.println(); + pw.print("Next deadline alarm in "); + TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw); + pw.println(); + pw.print("Tracking "); + pw.print(mTrackedJobs.size()); + pw.println(":"); + for (JobStatus ts : mTrackedJobs) { + if (!ts.shouldDump(filterUid)) { + continue; + } + pw.print(" #"); + ts.printUniqueId(pw); + pw.print(" from "); + UserHandle.formatUid(pw, ts.getSourceUid()); + pw.print(": Delay="); + if (ts.hasTimingDelayConstraint()) { + TimeUtils.formatDuration(ts.getEarliestRunTime(), nowElapsed, pw); + } else { + pw.print("N/A"); + } + pw.print(", Deadline="); + if (ts.hasDeadlineConstraint()) { + TimeUtils.formatDuration(ts.getLatestRunTimeElapsed(), nowElapsed, pw); + } else { + pw.print("N/A"); + } + pw.println(); + } + } +}
\ No newline at end of file |