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