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