| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.messaging.datamodel.action; |
| |
| import android.os.Bundle; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.text.TextUtils; |
| |
| import com.android.messaging.datamodel.DataModel; |
| import com.android.messaging.datamodel.DataModelException; |
| import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener; |
| import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener; |
| import com.android.messaging.util.LogUtil; |
| |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| /** |
| * Base class for operations that perform application business logic off the main UI thread while |
| * holding a wake lock. |
| * . |
| * Note all derived classes need to provide real implementation of Parcelable (this is abstract) |
| */ |
| public abstract class Action implements Parcelable { |
| private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; |
| |
| // Members holding the parameters common to all actions - no action state |
| public final String actionKey; |
| |
| // If derived classes keep their data in actionParameters then parcelable is trivial |
| protected Bundle actionParameters; |
| |
| // This does not get written to the parcel |
| private final List<Action> mBackgroundActions = new LinkedList<Action>(); |
| |
| /** |
| * Process the action locally - runs on action service thread. |
| * TODO: Currently, there is no way for this method to indicate failure |
| * @return result to be passed in to {@link ActionExecutedListener#onActionExecuted}. It is |
| * also the result passed in to {@link ActionCompletedListener#onActionSucceeded} if |
| * there is no background work. |
| */ |
| protected Object executeAction() { |
| return null; |
| } |
| |
| /** |
| * Queues up background work ie. {@link #doBackgroundWork} will be called on the |
| * background worker thread. |
| */ |
| protected void requestBackgroundWork() { |
| mBackgroundActions.add(this); |
| } |
| |
| /** |
| * Queues up background actions for background processing after the current action has |
| * completed its processing ({@link #executeAction}, {@link processBackgroundCompletion} |
| * or {@link #processBackgroundFailure}) on the Action thread. |
| * @param backgroundAction |
| */ |
| protected void requestBackgroundWork(final Action backgroundAction) { |
| mBackgroundActions.add(backgroundAction); |
| } |
| |
| /** |
| * Return flag indicating if any actions have been queued |
| */ |
| public boolean hasBackgroundActions() { |
| return !mBackgroundActions.isEmpty(); |
| } |
| |
| /** |
| * Send queued actions to the background worker provided |
| */ |
| public void sendBackgroundActions(final BackgroundWorker worker) { |
| worker.queueBackgroundWork(mBackgroundActions); |
| mBackgroundActions.clear(); |
| } |
| |
| /** |
| * Do work in a long running background worker thread. |
| * {@link #requestBackgroundWork} needs to be called for this method to |
| * be called. {@link #processBackgroundFailure} will be called on the Action service thread |
| * if this method throws {@link DataModelException}. |
| * @return response that is to be passed to {@link #processBackgroundResponse} |
| */ |
| protected Bundle doBackgroundWork() throws DataModelException { |
| return null; |
| } |
| |
| /** |
| * Process the success response from the background worker. Runs on action service thread. |
| * @param response the response returned by {@link #doBackgroundWork} |
| * @return result to be passed in to {@link ActionCompletedListener#onActionSucceeded} |
| */ |
| protected Object processBackgroundResponse(final Bundle response) { |
| return null; |
| } |
| |
| /** |
| * Called in case of failures when sending background actions. Runs on action service thread |
| * @return result to be passed in to {@link ActionCompletedListener#onActionFailed} |
| */ |
| protected Object processBackgroundFailure() { |
| return null; |
| } |
| |
| /** |
| * Constructor |
| */ |
| protected Action(final String key) { |
| this.actionKey = key; |
| this.actionParameters = new Bundle(); |
| } |
| |
| /** |
| * Constructor |
| */ |
| protected Action() { |
| this.actionKey = generateUniqueActionKey(getClass().getSimpleName()); |
| this.actionParameters = new Bundle(); |
| } |
| |
| /** |
| * Queue an action and monitor for processing by the ActionService via the factory helper |
| */ |
| protected void start(final ActionMonitor monitor) { |
| ActionMonitor.registerActionMonitor(this.actionKey, monitor); |
| DataModel.startActionService(this); |
| } |
| |
| /** |
| * Queue an action for processing by the ActionService via the factory helper |
| */ |
| public void start() { |
| DataModel.startActionService(this); |
| } |
| |
| /** |
| * Queue an action for delayed processing by the ActionService via the factory helper |
| */ |
| public void schedule(final int requestCode, final long delayMs) { |
| DataModel.scheduleAction(this, requestCode, delayMs); |
| } |
| |
| /** |
| * Called when action queues ActionService intent |
| */ |
| protected final void markStart() { |
| ActionMonitor.setState(this, ActionMonitor.STATE_CREATED, |
| ActionMonitor.STATE_QUEUED); |
| } |
| |
| /** |
| * Mark the beginning of local action execution |
| */ |
| protected final void markBeginExecute() { |
| ActionMonitor.setState(this, ActionMonitor.STATE_QUEUED, |
| ActionMonitor.STATE_EXECUTING); |
| } |
| |
| /** |
| * Mark the end of local action execution - either completes the action or queues |
| * background actions |
| */ |
| protected final void markEndExecute(final Object result) { |
| final boolean hasBackgroundActions = hasBackgroundActions(); |
| ActionMonitor.setExecutedState(this, ActionMonitor.STATE_EXECUTING, |
| hasBackgroundActions, result); |
| if (!hasBackgroundActions) { |
| ActionMonitor.setCompleteState(this, ActionMonitor.STATE_EXECUTING, |
| result, true); |
| } |
| } |
| |
| /** |
| * Update action state to indicate that the background worker is starting |
| */ |
| protected final void markBackgroundWorkStarting() { |
| ActionMonitor.setState(this, |
| ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED, |
| ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION); |
| } |
| |
| /** |
| * Update action state to indicate that the background worker has posted its response |
| * (or failure) to the Action service |
| */ |
| protected final void markBackgroundCompletionQueued() { |
| ActionMonitor.setState(this, |
| ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, |
| ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED); |
| } |
| |
| /** |
| * Update action state to indicate the background action failed but is being re-queued for retry |
| */ |
| protected final void markBackgroundWorkQueued() { |
| ActionMonitor.setState(this, |
| ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION, |
| ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED); |
| } |
| |
| /** |
| * Called by ActionService to process a response from the background worker |
| * @param response the response returned by {@link #doBackgroundWork} |
| */ |
| protected final void processBackgroundWorkResponse(final Bundle response) { |
| ActionMonitor.setState(this, |
| ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED, |
| ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE); |
| final Object result = processBackgroundResponse(response); |
| ActionMonitor.setCompleteState(this, |
| ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE, result, true); |
| } |
| |
| /** |
| * Called by ActionService when a background action fails |
| */ |
| protected final void processBackgroundWorkFailure() { |
| final Object result = processBackgroundFailure(); |
| ActionMonitor.setCompleteState(this, ActionMonitor.STATE_UNDEFINED, |
| result, false); |
| } |
| |
| private static final Object sLock = new Object(); |
| private static long sActionIdx = System.currentTimeMillis() * 1000; |
| |
| /** |
| * Helper method to generate a unique operation index |
| */ |
| protected static long getActionIdx() { |
| long idx = 0; |
| synchronized (sLock) { |
| idx = ++sActionIdx; |
| } |
| return idx; |
| } |
| |
| /** |
| * This helper can be used to generate a unique key used to identify an action. |
| * @param baseKey - key generated to identify the action parameters |
| * @return - composite key generated by appending unique index |
| */ |
| protected static String generateUniqueActionKey(final String baseKey) { |
| final StringBuilder key = new StringBuilder(); |
| if (!TextUtils.isEmpty(baseKey)) { |
| key.append(baseKey); |
| } |
| key.append(":"); |
| key.append(getActionIdx()); |
| return key.toString(); |
| } |
| |
| /** |
| * Most derived classes use this base implementation (unless they include files handles) |
| */ |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| /** |
| * Derived classes need to implement writeToParcel (but typically should call this method |
| * to parcel Action member variables before they parcel their member variables). |
| */ |
| public void writeActionToParcel(final Parcel parcel, final int flags) { |
| parcel.writeString(this.actionKey); |
| parcel.writeBundle(this.actionParameters); |
| } |
| |
| /** |
| * Helper for derived classes to implement parcelable |
| */ |
| public Action(final Parcel in) { |
| this.actionKey = in.readString(); |
| // Note: Need to set classloader to ensure we can un-parcel classes from this package |
| this.actionParameters = in.readBundle(Action.class.getClassLoader()); |
| } |
| } |