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