• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.phone.vvm.omtp.scheduling;
18 
19 import android.annotation.MainThread;
20 import android.annotation.Nullable;
21 import android.annotation.WorkerThread;
22 import android.app.AlarmManager;
23 import android.app.PendingIntent;
24 import android.app.Service;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.os.Binder;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.PowerManager;
34 import android.os.PowerManager.WakeLock;
35 import android.os.SystemClock;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.phone.Assert;
38 import com.android.phone.NeededForTesting;
39 import com.android.phone.vvm.omtp.VvmLog;
40 import com.android.phone.vvm.omtp.scheduling.Task.TaskId;
41 import java.util.ArrayDeque;
42 import java.util.Queue;
43 
44 /**
45  * A service to queue and run {@link Task} on a worker thread. Only one task will be ran at a time,
46  * and same task cannot exist in the queue at the same time. The service will be started when a
47  * intent is received, and stopped when there are no more tasks in the queue.
48  */
49 public class TaskSchedulerService extends Service {
50 
51     private static final String TAG = "VvmTaskScheduler";
52 
53     private static final String ACTION_WAKEUP = "action_wakeup";
54 
55     private static final int READY_TOLERANCE_MILLISECONDS = 100;
56 
57     /**
58      * Threshold to determine whether to do a short or long sleep when a task is scheduled in the
59      * future.
60      *
61      * <p>A short sleep will continue to held the wake lock and use {@link
62      * Handler#postDelayed(Runnable, long)} to wait for the next task.
63      *
64      * <p>A long sleep will release the wake lock and set a {@link AlarmManager} alarm. The alarm is
65      * exact and will wake up the device. Note: as this service is run in the telephony process it
66      * does not seem to be restricted by doze or sleep, it will fire exactly at the moment. The
67      * unbundled version should take doze into account.
68      */
69     private static final int SHORT_SLEEP_THRESHOLD_MILLISECONDS = 60_000;
70     /**
71      * When there are no more tasks to be run the service should be stopped. But when all tasks has
72      * finished there might still be more tasks in the message queue waiting to be processed,
73      * especially the ones submitted in {@link Task#onCompleted()}. Wait for a while before stopping
74      * the service to make sure there are no pending messages.
75      */
76     private static final int STOP_DELAY_MILLISECONDS = 5_000;
77     private static final String EXTRA_CLASS_NAME = "extra_class_name";
78 
79     private static final String WAKE_LOCK_TAG = "TaskSchedulerService_wakelock";
80 
81     // The thread to run tasks on
82     private volatile WorkerThreadHandler mWorkerThreadHandler;
83 
84     private Context mContext = this;
85     /**
86      * Used by tests to turn task handling into a single threaded process by calling {@link
87      * Handler#handleMessage(Message)} directly
88      */
89     private MessageSender mMessageSender = new MessageSender();
90 
91     private MainThreadHandler mMainThreadHandler;
92 
93     private WakeLock mWakeLock;
94 
95     /**
96      * Main thread only, access through {@link #getTasks()}
97      */
98     private final Queue<Task> mTasks = new ArrayDeque<>();
99     private boolean mWorkerThreadIsBusy = false;
100 
101     private final Runnable mStopServiceWithDelay = new Runnable() {
102         @Override
103         public void run() {
104             VvmLog.d(TAG, "Stopping service");
105             stopSelf();
106         }
107     };
108     /**
109      * Should attempt to run the next task when a task has finished or been added.
110      */
111     private boolean mTaskAutoRunDisabledForTesting = false;
112 
113     @VisibleForTesting
114     final class WorkerThreadHandler extends Handler {
115 
WorkerThreadHandler(Looper looper)116         public WorkerThreadHandler(Looper looper) {
117             super(looper);
118         }
119 
120         @Override
121         @WorkerThread
handleMessage(Message msg)122         public void handleMessage(Message msg) {
123             Assert.isNotMainThread();
124             Task task = (Task) msg.obj;
125             try {
126                 VvmLog.v(TAG, "executing task " + task);
127                 task.onExecuteInBackgroundThread();
128             } catch (Throwable throwable) {
129                 VvmLog.e(TAG, "Exception while executing task " + task + ":", throwable);
130             }
131 
132             Message schedulerMessage = mMainThreadHandler.obtainMessage();
133             schedulerMessage.obj = task;
134             mMessageSender.send(schedulerMessage);
135         }
136     }
137 
138     @VisibleForTesting
139     final class MainThreadHandler extends Handler {
140 
MainThreadHandler(Looper looper)141         public MainThreadHandler(Looper looper) {
142             super(looper);
143         }
144 
145         @Override
146         @MainThread
handleMessage(Message msg)147         public void handleMessage(Message msg) {
148             Assert.isMainThread();
149             Task task = (Task) msg.obj;
150             getTasks().remove(task);
151             task.onCompleted();
152             mWorkerThreadIsBusy = false;
153             maybeRunNextTask();
154         }
155     }
156 
157     @Override
158     @MainThread
onCreate()159     public void onCreate() {
160         super.onCreate();
161         mWakeLock = getSystemService(PowerManager.class)
162                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
163         mWakeLock.setReferenceCounted(false);
164         HandlerThread thread = new HandlerThread("VvmTaskSchedulerService");
165         thread.start();
166 
167         mWorkerThreadHandler = new WorkerThreadHandler(thread.getLooper());
168         mMainThreadHandler = new MainThreadHandler(Looper.getMainLooper());
169     }
170 
171     @Override
onDestroy()172     public void onDestroy() {
173         mWorkerThreadHandler.getLooper().quit();
174         mWakeLock.release();
175     }
176 
177     @Override
178     @MainThread
onStartCommand(@ullable Intent intent, int flags, int startId)179     public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
180         Assert.isMainThread();
181         // maybeRunNextTask() will release the wakelock either by entering a long sleep or stopping
182         // the service.
183         mWakeLock.acquire();
184         if (ACTION_WAKEUP.equals(intent.getAction())) {
185             VvmLog.d(TAG, "woke up by AlarmManager");
186         } else {
187             Task task = createTask(intent, flags, startId);
188             if (task == null) {
189                 VvmLog.e(TAG, "cannot create task form intent");
190             } else {
191                 addTask(task);
192             }
193         }
194         maybeRunNextTask();
195         // STICKY means the service will be automatically restarted will the last intent if it is
196         // killed.
197         return START_NOT_STICKY;
198     }
199 
200     @MainThread
201     @VisibleForTesting
addTask(Task task)202     void addTask(Task task) {
203         Assert.isMainThread();
204         if (task.getId().id == Task.TASK_INVALID) {
205             throw new AssertionError("Task id was not set to a valid value before adding.");
206         }
207         if (task.getId().id != Task.TASK_ALLOW_DUPLICATES) {
208             Task oldTask = getTask(task.getId());
209             if (oldTask != null) {
210                 oldTask.onDuplicatedTaskAdded(task);
211                 return;
212             }
213         }
214         mMainThreadHandler.removeCallbacks(mStopServiceWithDelay);
215         getTasks().add(task);
216         maybeRunNextTask();
217     }
218 
219     @MainThread
220     @Nullable
getTask(TaskId taskId)221     private Task getTask(TaskId taskId) {
222         Assert.isMainThread();
223         for (Task task : getTasks()) {
224             if (task.getId().equals(taskId)) {
225                 return task;
226             }
227         }
228         return null;
229     }
230 
231     @MainThread
getTasks()232     private Queue<Task> getTasks() {
233         Assert.isMainThread();
234         return mTasks;
235     }
236 
237     /**
238      * Create an intent that will queue the <code>task</code>
239      */
createIntent(Context context, Class<? extends Task> task)240     public static Intent createIntent(Context context, Class<? extends Task> task) {
241         Intent intent = new Intent(context, TaskSchedulerService.class);
242         intent.putExtra(EXTRA_CLASS_NAME, task.getName());
243         return intent;
244     }
245 
246     @VisibleForTesting
247     @MainThread
248     @Nullable
createTask(@ullable Intent intent, int flags, int startId)249     Task createTask(@Nullable Intent intent, int flags, int startId) {
250         Assert.isMainThread();
251         if (intent == null) {
252             return null;
253         }
254         String className = intent.getStringExtra(EXTRA_CLASS_NAME);
255         VvmLog.d(TAG, "create task:" + className);
256         if (className == null) {
257             throw new IllegalArgumentException("EXTRA_CLASS_NAME expected");
258         }
259         try {
260             Task task = (Task) Class.forName(className).newInstance();
261             task.onCreate(mContext, intent, flags, startId);
262             return task;
263         } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
264             throw new IllegalArgumentException(e);
265         }
266     }
267 
268     @MainThread
maybeRunNextTask()269     private void maybeRunNextTask() {
270         Assert.isMainThread();
271         if (mWorkerThreadIsBusy) {
272             return;
273         }
274         if (mTaskAutoRunDisabledForTesting) {
275             // If mTaskAutoRunDisabledForTesting is true, runNextTask() must be explicitly called
276             // to run the next task.
277             return;
278         }
279 
280         runNextTask();
281     }
282 
283     @VisibleForTesting
284     @MainThread
runNextTask()285     void runNextTask() {
286         Assert.isMainThread();
287         // The current alarm is no longer valid, a new one will be set up if required.
288         getSystemService(AlarmManager.class).cancel(getWakeupIntent());
289         if (getTasks().isEmpty()) {
290             prepareStop();
291             return;
292         }
293         Long minimalWaitTime = null;
294         for (Task task : getTasks()) {
295             long waitTime = task.getReadyInMilliSeconds();
296             if (waitTime < READY_TOLERANCE_MILLISECONDS) {
297                 task.onBeforeExecute();
298                 Message message = mWorkerThreadHandler.obtainMessage();
299                 message.obj = task;
300                 mWorkerThreadIsBusy = true;
301                 mMessageSender.send(message);
302                 return;
303             } else {
304                 if (minimalWaitTime == null || waitTime < minimalWaitTime) {
305                     minimalWaitTime = waitTime;
306                 }
307             }
308         }
309         VvmLog.d(TAG, "minimal wait time:" + minimalWaitTime);
310         if (!mTaskAutoRunDisabledForTesting && minimalWaitTime != null) {
311             // No tasks are currently ready. Sleep until the next one should be.
312             // If a new task is added during the sleep the service will wake immediately.
313             sleep(minimalWaitTime);
314         }
315     }
316 
sleep(long timeMillis)317     private void sleep(long timeMillis) {
318         if (timeMillis < SHORT_SLEEP_THRESHOLD_MILLISECONDS) {
319             mMainThreadHandler.postDelayed(new Runnable() {
320                 @Override
321                 public void run() {
322                     maybeRunNextTask();
323                 }
324             }, timeMillis);
325             return;
326         }
327 
328         // Tasks does not have a strict timing requirement, use AlarmManager.set() so the OS could
329         // optimize the battery usage. As this service currently run in the telephony process the
330         // OS give it privileges to behave the same as setExact(), but set() is the targeted
331         // behavior once this is unbundled.
332         getSystemService(AlarmManager.class).set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
333                 SystemClock.elapsedRealtime() + timeMillis,
334                 getWakeupIntent());
335         mWakeLock.release();
336         VvmLog.d(TAG, "Long sleep for " + timeMillis + " millis");
337     }
338 
getWakeupIntent()339     private PendingIntent getWakeupIntent() {
340         Intent intent = new Intent(ACTION_WAKEUP, null, this, getClass());
341         return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
342     }
343 
prepareStop()344     private void prepareStop() {
345         VvmLog.d(TAG,
346                 "No more tasks, stopping service if no task are added in "
347                         + STOP_DELAY_MILLISECONDS + " millis");
348         mMainThreadHandler.postDelayed(mStopServiceWithDelay, STOP_DELAY_MILLISECONDS);
349     }
350 
351     static class MessageSender {
352 
send(Message message)353         public void send(Message message) {
354             message.sendToTarget();
355         }
356     }
357 
358     @NeededForTesting
setContextForTest(Context context)359     void setContextForTest(Context context) {
360         mContext = context;
361     }
362 
363     @NeededForTesting
setTaskAutoRunDisabledForTest(boolean value)364     void setTaskAutoRunDisabledForTest(boolean value) {
365         mTaskAutoRunDisabledForTesting = value;
366     }
367 
368     @NeededForTesting
setMessageSenderForTest(MessageSender sender)369     void setMessageSenderForTest(MessageSender sender) {
370         mMessageSender = sender;
371     }
372 
373     @NeededForTesting
clearTasksForTest()374     void clearTasksForTest() {
375         mTasks.clear();
376     }
377 
378     @Override
379     @Nullable
onBind(Intent intent)380     public IBinder onBind(Intent intent) {
381         return new LocalBinder();
382     }
383 
384     @NeededForTesting
385     class LocalBinder extends Binder {
386 
387         @NeededForTesting
getService()388         public TaskSchedulerService getService() {
389             return TaskSchedulerService.this;
390         }
391     }
392 }
393