Home | History | Annotate | Download | only in action
      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      */
     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