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