1 /*
2  * Copyright (C) 2015 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.messaging.datamodel.action;
18 
19 import android.os.Bundle;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.text.TextUtils;
23 
24 import com.android.messaging.datamodel.DataModel;
25 import com.android.messaging.datamodel.DataModelException;
26 import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
27 import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener;
28 import com.android.messaging.util.LogUtil;
29 
30 import java.util.LinkedList;
31 import java.util.List;
32 
33 /**
34  * Base class for operations that perform application business logic off the main UI thread while
35  * holding a wake lock.
36  * .
37  * Note all derived classes need to provide real implementation of Parcelable (this is abstract)
38  */
39 public abstract class Action implements Parcelable {
40     private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
41 
42     // Members holding the parameters common to all actions - no action state
43     public final String actionKey;
44 
45     // If derived classes keep their data in actionParameters then parcelable is trivial
46     protected Bundle actionParameters;
47 
48     // This does not get written to the parcel
49     private final List<Action> mBackgroundActions = new LinkedList<Action>();
50 
51     /**
52      * Process the action locally - runs on action service thread.
53      * TODO: Currently, there is no way for this method to indicate failure
54      * @return result to be passed in to {@link ActionExecutedListener#onActionExecuted}. It is
55      *         also the result passed in to {@link ActionCompletedListener#onActionSucceeded} if
56      *         there is no background work.
57      */
executeAction()58     protected Object executeAction() {
59         return null;
60     }
61 
62     /**
63      * Queues up background work ie. {@link #doBackgroundWork} will be called on the
64      * background worker thread.
65      */
requestBackgroundWork()66     protected void requestBackgroundWork() {
67         mBackgroundActions.add(this);
68     }
69 
70     /**
71      * Queues up background actions for background processing after the current action has
72      * completed its processing ({@link #executeAction}, {@link processBackgroundCompletion}
73      * or {@link #processBackgroundFailure}) on the Action thread.
74      * @param backgroundAction
75      */
requestBackgroundWork(final Action backgroundAction)76     protected void requestBackgroundWork(final Action backgroundAction) {
77         mBackgroundActions.add(backgroundAction);
78     }
79 
80     /**
81      * Return flag indicating if any actions have been queued
82      */
hasBackgroundActions()83     public boolean hasBackgroundActions() {
84         return !mBackgroundActions.isEmpty();
85     }
86 
87     /**
88      * Send queued actions to the background worker provided
89      */
sendBackgroundActions(final BackgroundWorker worker)90     public void sendBackgroundActions(final BackgroundWorker worker) {
91         worker.queueBackgroundWork(mBackgroundActions);
92         mBackgroundActions.clear();
93     }
94 
95     /**
96      * Do work in a long running background worker thread.
97      * {@link #requestBackgroundWork} needs to be called for this method to
98      * be called. {@link #processBackgroundFailure} will be called on the Action service thread
99      * if this method throws {@link DataModelException}.
100      * @return response that is to be passed to {@link #processBackgroundResponse}
101      */
doBackgroundWork()102     protected Bundle doBackgroundWork() throws DataModelException {
103         return null;
104     }
105 
106     /**
107      * Process the success response from the background worker. Runs on action service thread.
108      * @param response the response returned by {@link #doBackgroundWork}
109      * @return result to be passed in to {@link ActionCompletedListener#onActionSucceeded}
110      */
processBackgroundResponse(final Bundle response)111     protected Object processBackgroundResponse(final Bundle response) {
112         return null;
113     }
114 
115     /**
116      * Called in case of failures when sending background actions. Runs on action service thread
117      * @return result to be passed in to {@link ActionCompletedListener#onActionFailed}
118      */
processBackgroundFailure()119     protected Object processBackgroundFailure() {
120         return null;
121     }
122 
123     /**
124      * Constructor
125      */
Action(final String key)126     protected Action(final String key) {
127         this.actionKey = key;
128         this.actionParameters = new Bundle();
129     }
130 
131     /**
132      * Constructor
133      */
Action()134     protected Action() {
135         this.actionKey = generateUniqueActionKey(getClass().getSimpleName());
136         this.actionParameters = new Bundle();
137     }
138 
139     /**
140      * Queue an action and monitor for processing by the ActionService via the factory helper
141      */
start(final ActionMonitor monitor)142     protected void start(final ActionMonitor monitor) {
143         ActionMonitor.registerActionMonitor(this.actionKey, monitor);
144         DataModel.startActionService(this);
145     }
146 
147     /**
148      * Queue an action for processing by the ActionService via the factory helper
149      */
start()150     public void start() {
151         DataModel.startActionService(this);
152     }
153 
154     /**
155      * Queue an action for delayed processing by the ActionService via the factory helper
156      */
schedule(final int requestCode, final long delayMs)157     public void schedule(final int requestCode, final long delayMs) {
158         DataModel.scheduleAction(this, requestCode, delayMs);
159     }
160 
161     /**
162      * Called when action queues ActionService intent
163      */
markStart()164     protected final void markStart() {
165         ActionMonitor.setState(this, ActionMonitor.STATE_CREATED,
166                 ActionMonitor.STATE_QUEUED);
167     }
168 
169     /**
170      * Mark the beginning of local action execution
171      */
markBeginExecute()172     protected final void markBeginExecute() {
173         ActionMonitor.setState(this, ActionMonitor.STATE_QUEUED,
174                 ActionMonitor.STATE_EXECUTING);
175     }
176 
177     /**
178      * Mark the end of local action execution - either completes the action or queues
179      * background actions
180      */
markEndExecute(final Object result)181     protected final void markEndExecute(final Object result) {
182         final boolean hasBackgroundActions = hasBackgroundActions();
183         ActionMonitor.setExecutedState(this, ActionMonitor.STATE_EXECUTING,
184                 hasBackgroundActions, result);
185         if (!hasBackgroundActions) {
186             ActionMonitor.setCompleteState(this, ActionMonitor.STATE_EXECUTING,
187                     result, true);
188         }
189     }
190 
191     /**
192      * Update action state to indicate that the background worker is starting
193      */
markBackgroundWorkStarting()194     protected final void markBackgroundWorkStarting() {
195         ActionMonitor.setState(this,
196                 ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED,
197                 ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION);
198     }
199 
200     /**
201      * Update action state to indicate that the background worker has posted its response
202      * (or failure) to the Action service
203      */
markBackgroundCompletionQueued()204     protected final void markBackgroundCompletionQueued() {
205         ActionMonitor.setState(this,
206                 ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
207                 ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED);
208     }
209 
210     /**
211      * Update action state to indicate the background action failed but is being re-queued for retry
212      */
markBackgroundWorkQueued()213     protected final void markBackgroundWorkQueued() {
214         ActionMonitor.setState(this,
215                 ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
216                 ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED);
217     }
218 
219     /**
220      * Called by ActionService to process a response from the background worker
221      * @param response the response returned by {@link #doBackgroundWork}
222      */
processBackgroundWorkResponse(final Bundle response)223     protected final void processBackgroundWorkResponse(final Bundle response) {
224         ActionMonitor.setState(this,
225                 ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED,
226                 ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE);
227         final Object result = processBackgroundResponse(response);
228         ActionMonitor.setCompleteState(this,
229                 ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE, result, true);
230     }
231 
232     /**
233      * Called by ActionService when a background action fails
234      */
processBackgroundWorkFailure()235     protected final void processBackgroundWorkFailure() {
236         final Object result = processBackgroundFailure();
237         ActionMonitor.setCompleteState(this, ActionMonitor.STATE_UNDEFINED,
238                 result, false);
239     }
240 
241     private static final Object sLock = new Object();
242     private static long sActionIdx = System.currentTimeMillis() * 1000;
243 
244     /**
245      * Helper method to generate a unique operation index
246      */
getActionIdx()247     protected static long getActionIdx() {
248         long idx = 0;
249         synchronized (sLock) {
250             idx = ++sActionIdx;
251         }
252         return idx;
253     }
254 
255     /**
256      * This helper can be used to generate a unique key used to identify an action.
257      * @param baseKey - key generated to identify the action parameters
258      * @return - composite key generated by appending unique index
259      */
generateUniqueActionKey(final String baseKey)260     protected static String generateUniqueActionKey(final String baseKey) {
261         final StringBuilder key = new StringBuilder();
262         if (!TextUtils.isEmpty(baseKey)) {
263             key.append(baseKey);
264         }
265         key.append(":");
266         key.append(getActionIdx());
267         return key.toString();
268     }
269 
270     /**
271      * Most derived classes use this base implementation (unless they include files handles)
272      */
273     @Override
describeContents()274     public int describeContents() {
275         return 0;
276     }
277 
278     /**
279      * Derived classes need to implement writeToParcel (but typically should call this method
280      * to parcel Action member variables before they parcel their member variables).
281      */
writeActionToParcel(final Parcel parcel, final int flags)282     public void writeActionToParcel(final Parcel parcel, final int flags) {
283         parcel.writeString(this.actionKey);
284         parcel.writeBundle(this.actionParameters);
285     }
286 
287     /**
288      * Helper for derived classes to implement parcelable
289      */
Action(final Parcel in)290     public Action(final Parcel in) {
291         this.actionKey = in.readString();
292         // Note: Need to set classloader to ensure we can un-parcel classes from this package
293         this.actionParameters = in.readBundle(Action.class.getClassLoader());
294     }
295 }
296