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.voicemail.impl.scheduling; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.os.Bundle; 22 import android.os.SystemClock; 23 import android.support.annotation.CallSuper; 24 import android.support.annotation.MainThread; 25 import android.support.annotation.NonNull; 26 import android.support.annotation.VisibleForTesting; 27 import android.support.annotation.WorkerThread; 28 import android.telecom.PhoneAccountHandle; 29 import com.android.dialer.proguard.UsedByReflection; 30 import com.android.voicemail.impl.Assert; 31 import com.android.voicemail.impl.NeededForTesting; 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * Provides common utilities for task implementations, such as execution time and managing {@link 37 * Policy} 38 */ 39 @UsedByReflection(value = "Tasks.java") 40 public abstract class BaseTask implements Task { 41 42 @VisibleForTesting 43 public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "extra_phone_account_handle"; 44 45 private static final String EXTRA_EXECUTION_TIME = "extra_execution_time"; 46 47 private Bundle extras; 48 49 private Context context; 50 51 private int id; 52 private PhoneAccountHandle phoneAccountHandle; 53 54 private boolean hasStarted; 55 private volatile boolean hasFailed; 56 57 @NonNull private final List<Policy> policies = new ArrayList<>(); 58 59 private long executionTime; 60 61 private static Clock clock = new Clock(); 62 BaseTask(int id)63 protected BaseTask(int id) { 64 this.id = id; 65 executionTime = getTimeMillis(); 66 } 67 68 /** 69 * Modify the task ID to prevent arbitrary task from executing. Can only be called before {@link 70 * #onCreate(Context, Bundle)} returns. 71 */ 72 @MainThread setId(int id)73 public void setId(int id) { 74 Assert.isMainThread(); 75 this.id = id; 76 } 77 78 @MainThread hasStarted()79 public boolean hasStarted() { 80 Assert.isMainThread(); 81 return hasStarted; 82 } 83 84 @MainThread hasFailed()85 public boolean hasFailed() { 86 Assert.isMainThread(); 87 return hasFailed; 88 } 89 getContext()90 public Context getContext() { 91 return context; 92 } 93 getPhoneAccountHandle()94 public PhoneAccountHandle getPhoneAccountHandle() { 95 return phoneAccountHandle; 96 } 97 /** 98 * Should be call in the constructor or {@link Policy#onCreate(BaseTask, Bundle)} will be missed. 99 */ 100 @MainThread addPolicy(Policy policy)101 public BaseTask addPolicy(Policy policy) { 102 Assert.isMainThread(); 103 policies.add(policy); 104 return this; 105 } 106 107 /** 108 * Indicate the task has failed. {@link Policy#onFail()} will be triggered once the execution 109 * ends. This mechanism is used by policies for actions such as determining whether to schedule a 110 * retry. Must be call inside {@link #onExecuteInBackgroundThread()} 111 */ 112 @WorkerThread fail()113 public void fail() { 114 Assert.isNotMainThread(); 115 hasFailed = true; 116 } 117 118 /** @param timeMillis the time since epoch, in milliseconds. */ 119 @MainThread setExecutionTime(long timeMillis)120 public void setExecutionTime(long timeMillis) { 121 Assert.isMainThread(); 122 executionTime = timeMillis; 123 } 124 getTimeMillis()125 public long getTimeMillis() { 126 return clock.getTimeMillis(); 127 } 128 129 /** 130 * Creates an intent that can be used to restart the current task. Derived class should build 131 * their intent upon this. 132 */ createRestartIntent()133 public Intent createRestartIntent() { 134 return createIntent(getContext(), this.getClass(), phoneAccountHandle); 135 } 136 137 /** 138 * Creates an intent that can be used to be broadcast to the {@link TaskReceiver}. Derived class 139 * should build their intent upon this. 140 */ createIntent( Context context, Class<? extends BaseTask> task, PhoneAccountHandle phoneAccountHandle)141 public static Intent createIntent( 142 Context context, Class<? extends BaseTask> task, PhoneAccountHandle phoneAccountHandle) { 143 Intent intent = Tasks.createIntent(context, task); 144 intent.putExtra(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle); 145 return intent; 146 } 147 148 @Override getId()149 public TaskId getId() { 150 return new TaskId(id, phoneAccountHandle); 151 } 152 153 @Override toBundle()154 public Bundle toBundle() { 155 extras.putLong(EXTRA_EXECUTION_TIME, executionTime); 156 return extras; 157 } 158 159 @Override 160 @CallSuper onCreate(Context context, Bundle extras)161 public void onCreate(Context context, Bundle extras) { 162 this.context = context; 163 this.extras = extras; 164 phoneAccountHandle = extras.getParcelable(EXTRA_PHONE_ACCOUNT_HANDLE); 165 for (Policy policy : policies) { 166 policy.onCreate(this, extras); 167 } 168 } 169 170 @Override 171 @CallSuper onRestore(Bundle extras)172 public void onRestore(Bundle extras) { 173 if (this.extras.containsKey(EXTRA_EXECUTION_TIME)) { 174 executionTime = extras.getLong(EXTRA_EXECUTION_TIME); 175 } 176 } 177 178 @Override getReadyInMilliSeconds()179 public long getReadyInMilliSeconds() { 180 return executionTime - getTimeMillis(); 181 } 182 183 @Override 184 @CallSuper onBeforeExecute()185 public void onBeforeExecute() { 186 for (Policy policy : policies) { 187 policy.onBeforeExecute(); 188 } 189 hasStarted = true; 190 } 191 192 @Override 193 @CallSuper onCompleted()194 public void onCompleted() { 195 if (hasFailed) { 196 for (Policy policy : policies) { 197 policy.onFail(); 198 } 199 } 200 201 for (Policy policy : policies) { 202 policy.onCompleted(); 203 } 204 } 205 206 @Override onDuplicatedTaskAdded(Task task)207 public void onDuplicatedTaskAdded(Task task) { 208 for (Policy policy : policies) { 209 policy.onDuplicatedTaskAdded(); 210 } 211 } 212 213 @NeededForTesting 214 static class Clock { 215 getTimeMillis()216 public long getTimeMillis() { 217 return SystemClock.elapsedRealtime(); 218 } 219 } 220 221 /** Used to replace the clock with an deterministic clock */ 222 @NeededForTesting setClockForTesting(Clock clock)223 static void setClockForTesting(Clock clock) { 224 BaseTask.clock = clock; 225 } 226 } 227