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