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
17package com.android.messaging.datamodel.action;
18
19import android.os.Bundle;
20import android.os.Parcel;
21import android.os.Parcelable;
22import android.text.TextUtils;
23
24import com.android.messaging.datamodel.DataModel;
25import com.android.messaging.datamodel.DataModelException;
26import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
27import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener;
28import com.android.messaging.util.LogUtil;
29
30import java.util.LinkedList;
31import 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 */
39public 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     */
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     */
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     */
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     */
83    public boolean hasBackgroundActions() {
84        return !mBackgroundActions.isEmpty();
85    }
86
87    /**
88     * Send queued actions to the background worker provided
89     */
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     */
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     */
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     */
119    protected Object processBackgroundFailure() {
120        return null;
121    }
122
123    /**
124     * Constructor
125     */
126    protected Action(final String key) {
127        this.actionKey = key;
128        this.actionParameters = new Bundle();
129    }
130
131    /**
132     * Constructor
133     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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