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.Handler;
     20 import android.support.v4.util.SimpleArrayMap;
     21 import android.text.TextUtils;
     22 
     23 import com.android.messaging.util.Assert.RunsOnAnyThread;
     24 import com.android.messaging.util.Assert.RunsOnMainThread;
     25 import com.android.messaging.util.LogUtil;
     26 import com.android.messaging.util.ThreadUtil;
     27 import com.google.common.annotations.VisibleForTesting;
     28 
     29 import java.text.SimpleDateFormat;
     30 import java.util.Date;
     31 import java.util.TimeZone;
     32 
     33 /**
     34  * Base class for action monitors
     35  * Actions come in various flavors but
     36  *  o) Fire and forget - no monitor
     37  *  o) Immediate local processing only - will trigger ActionCompletedListener when done
     38  *  o) Background worker processing only - will trigger ActionCompletedListener when done
     39  *  o) Immediate local processing followed by background work followed by more local processing
     40  *      - will trigger ActionExecutedListener once local processing complete and
     41  *        ActionCompletedListener when second set of local process (dealing with background
     42  *         worker response) is complete
     43  */
     44 public class ActionMonitor {
     45     private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
     46 
     47     /**
     48      * Interface used to notify on completion of local execution for an action
     49      */
     50     public interface ActionExecutedListener {
     51         /**
     52          * @param result value returned by {@link Action#executeAction}
     53          */
     54         @RunsOnMainThread
     55         abstract void onActionExecuted(ActionMonitor monitor, final Action action,
     56                 final Object data, final Object result);
     57     }
     58 
     59     /**
     60      * Interface used to notify action completion
     61      */
     62     public interface ActionCompletedListener {
     63         /**
     64          * @param result object returned from processing the action. This is the value returned by
     65          *               {@link Action#executeAction} if there is no background work, or
     66          *               else the value returned by
     67          *               {@link Action#processBackgroundResponse}
     68          */
     69         @RunsOnMainThread
     70         abstract void onActionSucceeded(ActionMonitor monitor,
     71                 final Action action, final Object data, final Object result);
     72         /**
     73          * @param result value returned by {@link Action#processBackgroundFailure}
     74          */
     75         @RunsOnMainThread
     76         abstract void onActionFailed(ActionMonitor monitor, final Action action,
     77                 final Object data, final Object result);
     78     }
     79 
     80     /**
     81      * Interface for being notified of action state changes - used for profiling, testing only
     82      */
     83     protected interface ActionStateChangedListener {
     84         /**
     85          * @param action the action that is changing state
     86          * @param state the new state of the action
     87          */
     88         @RunsOnAnyThread
     89         void onActionStateChanged(Action action, int state);
     90     }
     91 
     92     /**
     93      * Operations always start out as STATE_CREATED and finish as STATE_COMPLETE.
     94      * Some common state transition sequences in between include:
     95      * <ul>
     96      *   <li>Local data change only : STATE_QUEUED - STATE_EXECUTING
     97      *   <li>Background worker request only : STATE_BACKGROUND_ACTIONS_QUEUED
     98      *      - STATE_EXECUTING_BACKGROUND_ACTION
     99      *      - STATE_BACKGROUND_COMPLETION_QUEUED
    100      *      - STATE_PROCESSING_BACKGROUND_RESPONSE
    101      *   <li>Local plus background worker request : STATE_QUEUED - STATE_EXECUTING
    102      *      - STATE_BACKGROUND_ACTIONS_QUEUED
    103      *      - STATE_EXECUTING_BACKGROUND_ACTION
    104      *      - STATE_BACKGROUND_COMPLETION_QUEUED
    105      *      - STATE_PROCESSING_BACKGROUND_RESPONSE
    106      * </ul>
    107      */
    108     protected static final int STATE_UNDEFINED = 0;
    109     protected static final int STATE_CREATED = 1; // Just created
    110     protected static final int STATE_QUEUED = 2; // Action queued for processing
    111     protected static final int STATE_EXECUTING = 3; // Action processing on datamodel thread
    112     protected static final int STATE_BACKGROUND_ACTIONS_QUEUED = 4;
    113     protected static final int STATE_EXECUTING_BACKGROUND_ACTION = 5;
    114     // The background work has completed, either returning a success response or resulting in a
    115     // failure
    116     protected static final int STATE_BACKGROUND_COMPLETION_QUEUED = 6;
    117     protected static final int STATE_PROCESSING_BACKGROUND_RESPONSE = 7;
    118     protected static final int STATE_COMPLETE = 8; // Action complete
    119 
    120     /**
    121      * Lock used to protect access to state and listeners
    122      */
    123     private final Object mLock = new Object();
    124 
    125     /**
    126      * Current state of action
    127      */
    128     @VisibleForTesting
    129     protected int mState;
    130 
    131     /**
    132      * Listener which is notified on action completion
    133      */
    134     private ActionCompletedListener mCompletedListener;
    135 
    136     /**
    137      * Listener which is notified on action executed
    138      */
    139     private ActionExecutedListener mExecutedListener;
    140 
    141     /**
    142      * Listener which is notified of state changes
    143      */
    144     private ActionStateChangedListener mStateChangedListener;
    145 
    146     /**
    147      * Handler used to post results back to caller
    148      */
    149     private final Handler mHandler;
    150 
    151     /**
    152      * Data passed back to listeners (associated with the action when it is created)
    153      */
    154     private final Object mData;
    155 
    156     /**
    157      * The action key is used to determine equivalence of operations and their requests
    158      */
    159     private final String mActionKey;
    160 
    161     /**
    162      * Get action key identifying associated action
    163      */
    164     public String getActionKey() {
    165         return mActionKey;
    166     }
    167 
    168     /**
    169      * Unregister listeners so that they will not be called back - override this method if needed
    170      */
    171     public void unregister() {
    172         clearListeners();
    173     }
    174 
    175     /**
    176      * Unregister listeners so that they will not be called
    177      */
    178     protected final void clearListeners() {
    179         synchronized (mLock) {
    180             mCompletedListener = null;
    181             mExecutedListener = null;
    182         }
    183     }
    184 
    185     /**
    186      * Create a monitor associated with a particular action instance
    187      */
    188     protected ActionMonitor(final int initialState, final String actionKey,
    189             final Object data) {
    190         mHandler = ThreadUtil.getMainThreadHandler();
    191         mActionKey = actionKey;
    192         mState = initialState;
    193         mData = data;
    194     }
    195 
    196     /**
    197      * Return flag to indicate if action is complete
    198      */
    199     public boolean isComplete() {
    200         boolean complete = false;
    201         synchronized (mLock) {
    202             complete = (mState == STATE_COMPLETE);
    203         }
    204         return complete;
    205     }
    206 
    207     /**
    208      * Set listener that will be called with action completed result
    209      */
    210     protected final void setCompletedListener(final ActionCompletedListener listener) {
    211         synchronized (mLock) {
    212             mCompletedListener = listener;
    213         }
    214     }
    215 
    216     /**
    217      * Set listener that will be called with local execution result
    218      */
    219     protected final void setExecutedListener(final ActionExecutedListener listener) {
    220         synchronized (mLock) {
    221             mExecutedListener = listener;
    222         }
    223     }
    224 
    225     /**
    226      * Set listener that will be called with local execution result
    227      */
    228     protected final void setStateChangedListener(final ActionStateChangedListener listener) {
    229         synchronized (mLock) {
    230             mStateChangedListener = listener;
    231         }
    232     }
    233 
    234     /**
    235      * Perform a state update transition
    236      * @param action - action whose state is updating
    237      * @param expectedOldState - expected existing state of action (can be UNKNOWN)
    238      * @param newState - new state which will be set
    239      */
    240     @VisibleForTesting
    241     protected void updateState(final Action action, final int expectedOldState,
    242             final int newState) {
    243         ActionStateChangedListener listener = null;
    244         synchronized (mLock) {
    245             if (expectedOldState != STATE_UNDEFINED &&
    246                     mState != expectedOldState) {
    247                 throw new IllegalStateException("On updateState to " + newState + " was " + mState
    248                         + " expecting " + expectedOldState);
    249             }
    250             if (newState != mState) {
    251                 mState = newState;
    252                 listener = mStateChangedListener;
    253             }
    254         }
    255         if (listener != null) {
    256             listener.onActionStateChanged(action, newState);
    257         }
    258     }
    259 
    260     /**
    261      * Perform a state update transition
    262      * @param action - action whose state is updating
    263      * @param expectedOldState - expected existing state of action (can be UNKNOWN)
    264      * @param newState - new state which will be set
    265      */
    266     static void setState(final Action action, final int expectedOldState,
    267             final int newState) {
    268         int oldMonitorState = expectedOldState;
    269         int newMonitorState = newState;
    270         final ActionMonitor monitor
    271                 = ActionMonitor.lookupActionMonitor(action.actionKey);
    272         if (monitor != null) {
    273             oldMonitorState = monitor.mState;
    274             monitor.updateState(action, expectedOldState, newState);
    275             newMonitorState = monitor.mState;
    276         }
    277         if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    278             final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    279             df.setTimeZone(TimeZone.getTimeZone("UTC"));
    280             LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
    281                     + "UTC State = " + oldMonitorState + " - " + newMonitorState);
    282         }
    283     }
    284 
    285     /**
    286      * Mark action complete
    287      * @param action - action whose state is updating
    288      * @param expectedOldState - expected existing state of action (can be UNKNOWN)
    289      * @param result - object returned from processing the action. This is the value returned by
    290      *                 {@link Action#executeAction} if there is no background work, or
    291      *                 else the value returned by {@link Action#processBackgroundResponse}
    292      *                 or {@link Action#processBackgroundFailure}
    293      */
    294     private final void complete(final Action action,
    295             final int expectedOldState, final Object result,
    296             final boolean succeeded) {
    297         ActionCompletedListener completedListener = null;
    298         synchronized (mLock) {
    299             setState(action, expectedOldState, STATE_COMPLETE);
    300             completedListener = mCompletedListener;
    301             mExecutedListener = null;
    302             mStateChangedListener = null;
    303         }
    304         if (completedListener != null) {
    305             // Marshal to UI thread
    306             mHandler.post(new Runnable() {
    307                 @Override
    308                 public void run() {
    309                     ActionCompletedListener listener = null;
    310                     synchronized (mLock) {
    311                         if (mCompletedListener != null) {
    312                             listener = mCompletedListener;
    313                         }
    314                         mCompletedListener = null;
    315                     }
    316                     if (listener != null) {
    317                         if (succeeded) {
    318                             listener.onActionSucceeded(ActionMonitor.this,
    319                                     action, mData, result);
    320                         } else {
    321                             listener.onActionFailed(ActionMonitor.this,
    322                                     action, mData, result);
    323                         }
    324                     }
    325                 }
    326             });
    327         }
    328     }
    329 
    330     /**
    331      * Mark action complete
    332      * @param action - action whose state is updating
    333      * @param expectedOldState - expected existing state of action (can be UNKNOWN)
    334      * @param result - object returned from processing the action. This is the value returned by
    335      *                 {@link Action#executeAction} if there is no background work, or
    336      *                 else the value returned by {@link Action#processBackgroundResponse}
    337      *                 or {@link Action#processBackgroundFailure}
    338      */
    339     static void setCompleteState(final Action action, final int expectedOldState,
    340             final Object result, final boolean succeeded) {
    341         int oldMonitorState = expectedOldState;
    342         final ActionMonitor monitor
    343                 = ActionMonitor.lookupActionMonitor(action.actionKey);
    344         if (monitor != null) {
    345             oldMonitorState = monitor.mState;
    346             monitor.complete(action, expectedOldState, result, succeeded);
    347             unregisterActionMonitorIfComplete(action.actionKey, monitor);
    348         }
    349         if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    350             final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    351             df.setTimeZone(TimeZone.getTimeZone("UTC"));
    352             LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
    353                     + "UTC State = " + oldMonitorState + " - " + STATE_COMPLETE);
    354         }
    355     }
    356 
    357     /**
    358      * Mark action complete
    359      * @param action - action whose state is updating
    360      * @param expectedOldState - expected existing state of action (can be UNKNOWN)
    361      * @param hasBackgroundActions - has the completing action requested background work
    362      * @param result - the return value of {@link Action#executeAction}
    363      */
    364     final void executed(final Action action,
    365             final int expectedOldState, final boolean hasBackgroundActions, final Object result) {
    366         ActionExecutedListener executedListener = null;
    367         synchronized (mLock) {
    368             if (hasBackgroundActions) {
    369                 setState(action, expectedOldState, STATE_BACKGROUND_ACTIONS_QUEUED);
    370             }
    371             executedListener = mExecutedListener;
    372         }
    373         if (executedListener != null) {
    374             // Marshal to UI thread
    375             mHandler.post(new Runnable() {
    376                 @Override
    377                 public void run() {
    378                     ActionExecutedListener listener = null;
    379                     synchronized (mLock) {
    380                         if (mExecutedListener != null) {
    381                             listener = mExecutedListener;
    382                             mExecutedListener = null;
    383                         }
    384                     }
    385                     if (listener != null) {
    386                         listener.onActionExecuted(ActionMonitor.this,
    387                                 action, mData, result);
    388                     }
    389                 }
    390             });
    391         }
    392     }
    393 
    394     /**
    395      * Mark action complete
    396      * @param action - action whose state is updating
    397      * @param expectedOldState - expected existing state of action (can be UNKNOWN)
    398      * @param hasBackgroundActions - has the completing action requested background work
    399      * @param result - the return value of {@link Action#executeAction}
    400      */
    401     static void setExecutedState(final Action action,
    402             final int expectedOldState, final boolean hasBackgroundActions, final Object result) {
    403         int oldMonitorState = expectedOldState;
    404         final ActionMonitor monitor
    405                 = ActionMonitor.lookupActionMonitor(action.actionKey);
    406         if (monitor != null) {
    407             oldMonitorState = monitor.mState;
    408             monitor.executed(action, expectedOldState, hasBackgroundActions, result);
    409         }
    410         if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
    411             final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    412             df.setTimeZone(TimeZone.getTimeZone("UTC"));
    413             LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
    414                     + "UTC State = " + oldMonitorState + " - EXECUTED");
    415         }
    416     }
    417 
    418     /**
    419      * Map of action monitors indexed by actionKey
    420      */
    421     @VisibleForTesting
    422     static SimpleArrayMap<String, ActionMonitor> sActionMonitors =
    423             new SimpleArrayMap<String, ActionMonitor>();
    424 
    425     /**
    426      * Insert new monitor into map
    427      */
    428     static void registerActionMonitor(final String actionKey,
    429             final ActionMonitor monitor) {
    430         if (monitor != null
    431                 && (TextUtils.isEmpty(monitor.getActionKey())
    432                         || TextUtils.isEmpty(actionKey)
    433                         || !actionKey.equals(monitor.getActionKey()))) {
    434             throw new IllegalArgumentException("Monitor key " + monitor.getActionKey()
    435                     + " not compatible with action key " + actionKey);
    436         }
    437         synchronized (sActionMonitors) {
    438             sActionMonitors.put(actionKey, monitor);
    439         }
    440     }
    441 
    442     /**
    443      * Find monitor associated with particular action
    444      */
    445     private static ActionMonitor lookupActionMonitor(final String actionKey) {
    446         ActionMonitor monitor = null;
    447         synchronized (sActionMonitors) {
    448             monitor = sActionMonitors.get(actionKey);
    449         }
    450         return monitor;
    451     }
    452 
    453     /**
    454      * Remove monitor from map
    455      */
    456     @VisibleForTesting
    457     static void unregisterActionMonitor(final String actionKey,
    458             final ActionMonitor monitor) {
    459         if (monitor != null) {
    460             synchronized (sActionMonitors) {
    461                 sActionMonitors.remove(actionKey);
    462             }
    463         }
    464     }
    465 
    466     /**
    467      * Remove monitor from map if the action is complete
    468      */
    469     static void unregisterActionMonitorIfComplete(final String actionKey,
    470             final ActionMonitor monitor) {
    471         if (monitor != null && monitor.isComplete()) {
    472             synchronized (sActionMonitors) {
    473                 sActionMonitors.remove(actionKey);
    474             }
    475         }
    476     }
    477 }
    478