OSDN Git Service

Fixing to fail android.JobScheduler.TimingConstraintsTest#testSchedulePeriodic in...
[android-x86/frameworks-base.git] / services / core / java / com / android / server / job / controllers / TimeController.java
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16
17 package com.android.server.job.controllers;
18
19 import android.app.AlarmManager;
20 import android.app.PendingIntent;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.os.SystemClock;
26 import android.util.Slog;
27
28 import com.android.server.job.JobSchedulerService;
29 import com.android.server.job.StateChangedListener;
30
31 import java.io.PrintWriter;
32 import java.util.Iterator;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.ListIterator;
36
37 /**
38  * This class sets an alarm for the next expiring job, and determines whether a job's minimum
39  * delay has been satisfied.
40  */
41 public class TimeController extends StateController {
42     private static final String TAG = "JobScheduler.Time";
43     private static final String ACTION_JOB_EXPIRED =
44             "android.content.jobscheduler.JOB_DEADLINE_EXPIRED";
45     private static final String ACTION_JOB_DELAY_EXPIRED =
46             "android.content.jobscheduler.JOB_DELAY_EXPIRED";
47
48     /** Set an alarm for the next job expiry. */
49     private final PendingIntent mDeadlineExpiredAlarmIntent;
50     /** Set an alarm for the next job delay expiry. This*/
51     private final PendingIntent mNextDelayExpiredAlarmIntent;
52
53     private long mNextJobExpiredElapsedMillis;
54     private long mNextDelayExpiredElapsedMillis;
55
56     private AlarmManager mAlarmService = null;
57     /** List of tracked jobs, sorted asc. by deadline */
58     private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
59     /** Singleton. */
60     private static TimeController mSingleton;
61
62     public static synchronized TimeController get(JobSchedulerService jms) {
63         if (mSingleton == null) {
64             mSingleton = new TimeController(jms, jms.getContext());
65         }
66         return mSingleton;
67     }
68
69     private TimeController(StateChangedListener stateChangedListener, Context context) {
70         super(stateChangedListener, context);
71         mDeadlineExpiredAlarmIntent =
72                 PendingIntent.getBroadcast(mContext, 0 /* ignored */,
73                         new Intent(ACTION_JOB_EXPIRED), 0);
74         mNextDelayExpiredAlarmIntent =
75                 PendingIntent.getBroadcast(mContext, 0 /* ignored */,
76                         new Intent(ACTION_JOB_DELAY_EXPIRED), 0);
77         mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
78         mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
79
80         // Register BR for these intents.
81         IntentFilter intentFilter = new IntentFilter(ACTION_JOB_EXPIRED);
82         intentFilter.addAction(ACTION_JOB_DELAY_EXPIRED);
83         mContext.registerReceiver(mAlarmExpiredReceiver, intentFilter);
84     }
85
86     /**
87      * Check if the job has a timing constraint, and if so determine where to insert it in our
88      * list.
89      */
90     @Override
91     public synchronized void maybeStartTrackingJob(JobStatus job) {
92         if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
93             maybeStopTrackingJob(job);
94             boolean isInsert = false;
95             ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
96             while (it.hasPrevious()) {
97                 JobStatus ts = it.previous();
98                 if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
99                     // Insert
100                     isInsert = true;
101                     break;
102                 }
103             }
104             if(isInsert)
105             {
106                 it.next();
107             }
108             it.add(job);
109             maybeUpdateAlarms(
110                     job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
111                     job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE);
112         }
113     }
114
115     /**
116      * When we stop tracking a job, we only need to update our alarms if the job we're no longer
117      * tracking was the one our alarms were based off of.
118      * Really an == comparison should be enough, but why play with fate? We'll do <=.
119      */
120     @Override
121     public synchronized void maybeStopTrackingJob(JobStatus job) {
122         if (mTrackedJobs.remove(job)) {
123             checkExpiredDelaysAndResetAlarm();
124             checkExpiredDeadlinesAndResetAlarm();
125         }
126     }
127
128     /**
129      * Determines whether this controller can stop tracking the given job.
130      * The controller is no longer interested in a job once its time constraint is satisfied, and
131      * the job's deadline is fulfilled - unlike other controllers a time constraint can't toggle
132      * back and forth.
133      */
134     private boolean canStopTrackingJob(JobStatus job) {
135         return (!job.hasTimingDelayConstraint() ||
136                 job.timeDelayConstraintSatisfied.get()) &&
137                 (!job.hasDeadlineConstraint() ||
138                         job.deadlineConstraintSatisfied.get());
139     }
140
141     private void ensureAlarmService() {
142         if (mAlarmService == null) {
143             mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
144         }
145     }
146
147     /**
148      * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
149      * if so, removing them from this list, and updating the alarm for the next expiry time.
150      */
151     private synchronized void checkExpiredDeadlinesAndResetAlarm() {
152         long nextExpiryTime = Long.MAX_VALUE;
153         final long nowElapsedMillis = SystemClock.elapsedRealtime();
154
155         Iterator<JobStatus> it = mTrackedJobs.iterator();
156         while (it.hasNext()) {
157             JobStatus job = it.next();
158             if (!job.hasDeadlineConstraint()) {
159                 continue;
160             }
161             final long jobDeadline = job.getLatestRunTimeElapsed();
162
163             if (jobDeadline <= nowElapsedMillis) {
164                 job.deadlineConstraintSatisfied.set(true);
165                 mStateChangedListener.onRunJobNow(job);
166                 it.remove();
167             } else {  // Sorted by expiry time, so take the next one and stop.
168                 nextExpiryTime = jobDeadline;
169                 break;
170             }
171         }
172         setDeadlineExpiredAlarm(nextExpiryTime);
173     }
174
175     /**
176      * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
177      * tracked jobs and marks them as ready as appropriate.
178      */
179     private synchronized void checkExpiredDelaysAndResetAlarm() {
180         final long nowElapsedMillis = SystemClock.elapsedRealtime();
181         long nextDelayTime = Long.MAX_VALUE;
182         boolean ready = false;
183         Iterator<JobStatus> it = mTrackedJobs.iterator();
184         while (it.hasNext()) {
185             final JobStatus job = it.next();
186             if (!job.hasTimingDelayConstraint()) {
187                 continue;
188             }
189             final long jobDelayTime = job.getEarliestRunTime();
190             if (jobDelayTime <= nowElapsedMillis) {
191                 job.timeDelayConstraintSatisfied.set(true);
192                 if (canStopTrackingJob(job)) {
193                     it.remove();
194                 }
195                 if (job.isReady()) {
196                     ready = true;
197                 }
198             } else {  // Keep going through list to get next delay time.
199                 if (nextDelayTime > jobDelayTime) {
200                     nextDelayTime = jobDelayTime;
201                 }
202             }
203         }
204         if (ready) {
205             mStateChangedListener.onControllerStateChanged();
206         }
207         setDelayExpiredAlarm(nextDelayTime);
208     }
209
210     private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
211         if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
212             setDelayExpiredAlarm(delayExpiredElapsed);
213         }
214         if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
215             setDeadlineExpiredAlarm(deadlineExpiredElapsed);
216         }
217     }
218
219     /**
220      * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
221      * delay will expire.
222      * This alarm <b>will not</b> wake up the phone.
223      */
224     private void setDelayExpiredAlarm(long alarmTimeElapsedMillis) {
225         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
226         mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
227         updateAlarmWithPendingIntent(mNextDelayExpiredAlarmIntent, mNextDelayExpiredElapsedMillis);
228     }
229
230     /**
231      * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
232      * deadline will expire.
233      * This alarm <b>will</b> wake up the phone.
234      */
235     private void setDeadlineExpiredAlarm(long alarmTimeElapsedMillis) {
236         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
237         mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
238         updateAlarmWithPendingIntent(mDeadlineExpiredAlarmIntent, mNextJobExpiredElapsedMillis);
239     }
240
241     private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
242         final long earliestWakeupTimeElapsed = SystemClock.elapsedRealtime();
243         if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
244             return earliestWakeupTimeElapsed;
245         }
246         return proposedAlarmTimeElapsedMillis;
247     }
248
249     private void updateAlarmWithPendingIntent(PendingIntent pi, long alarmTimeElapsed) {
250         ensureAlarmService();
251         if (alarmTimeElapsed == Long.MAX_VALUE) {
252             mAlarmService.cancel(pi);
253         } else {
254             if (DEBUG) {
255                 Slog.d(TAG, "Setting " + pi.getIntent().getAction() + " for: " + alarmTimeElapsed);
256             }
257             mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsed, pi);
258         }
259     }
260
261     private final BroadcastReceiver mAlarmExpiredReceiver = new BroadcastReceiver() {
262         @Override
263         public void onReceive(Context context, Intent intent) {
264             if (DEBUG) {
265                 Slog.d(TAG, "Just received alarm: " + intent.getAction());
266             }
267             // A job has just expired, so we run through the list of jobs that we have and
268             // notify our StateChangedListener.
269             if (ACTION_JOB_EXPIRED.equals(intent.getAction())) {
270                 checkExpiredDeadlinesAndResetAlarm();
271             } else if (ACTION_JOB_DELAY_EXPIRED.equals(intent.getAction())) {
272                 checkExpiredDelaysAndResetAlarm();
273             }
274         }
275     };
276
277     @Override
278     public void dumpControllerState(PrintWriter pw) {
279         final long nowElapsed = SystemClock.elapsedRealtime();
280         pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")");
281         pw.println(
282                 "Next delay alarm in " + (mNextDelayExpiredElapsedMillis - nowElapsed)/1000 + "s");
283         pw.println("Next deadline alarm in " + (mNextJobExpiredElapsedMillis - nowElapsed)/1000
284                 + "s");
285         pw.println("Tracking:");
286         for (JobStatus ts : mTrackedJobs) {
287             pw.println(String.valueOf(ts.hashCode()).substring(0, 3) + ".."
288                     + ": (" + (ts.hasTimingDelayConstraint() ? ts.getEarliestRunTime() : "N/A")
289                     + ", " + (ts.hasDeadlineConstraint() ?ts.getLatestRunTimeElapsed() : "N/A")
290                     + ")");
291         }
292     }
293 }