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 androidx.collection.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 onActionExecuted(ActionMonitor monitor, final Action action, final Object data, final Object result)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 onActionSucceeded(ActionMonitor monitor, final Action action, final Object data, final Object result)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 onActionFailed(ActionMonitor monitor, final Action action, final Object data, final Object result)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 onActionStateChanged(Action action, int state)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 */ getActionKey()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 */ unregister()171 public void unregister() { 172 clearListeners(); 173 } 174 175 /** 176 * Unregister listeners so that they will not be called 177 */ clearListeners()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 */ ActionMonitor(final int initialState, final String actionKey, final Object data)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 */ isComplete()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 */ setCompletedListener(final ActionCompletedListener listener)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 */ setExecutedListener(final ActionExecutedListener listener)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 */ setStateChangedListener(final ActionStateChangedListener listener)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 updateState(final Action action, final int expectedOldState, final int newState)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 */ setState(final Action action, final int expectedOldState, final int newState)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 */ complete(final Action action, final int expectedOldState, final Object result, final boolean succeeded)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 */ setCompleteState(final Action action, final int expectedOldState, final Object result, final boolean succeeded)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 */ executed(final Action action, final int expectedOldState, final boolean hasBackgroundActions, final Object result)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 */ setExecutedState(final Action action, final int expectedOldState, final boolean hasBackgroundActions, final Object result)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 */ registerActionMonitor(final String actionKey, final ActionMonitor monitor)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 */ lookupActionMonitor(final String actionKey)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 unregisterActionMonitor(final String actionKey, final ActionMonitor monitor)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 */ unregisterActionMonitorIfComplete(final String actionKey, final ActionMonitor monitor)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