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 */ executeAction()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 */ requestBackgroundWork()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 */ requestBackgroundWork(final Action backgroundAction)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 */ hasBackgroundActions()83 public boolean hasBackgroundActions() { 84 return !mBackgroundActions.isEmpty(); 85 } 86 87 /** 88 * Send queued actions to the background worker provided 89 */ sendBackgroundActions(final BackgroundWorker worker)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 */ doBackgroundWork()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 */ processBackgroundResponse(final Bundle response)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 */ processBackgroundFailure()119 protected Object processBackgroundFailure() { 120 return null; 121 } 122 123 /** 124 * Constructor 125 */ Action(final String key)126 protected Action(final String key) { 127 this.actionKey = key; 128 this.actionParameters = new Bundle(); 129 } 130 131 /** 132 * Constructor 133 */ Action()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 */ start(final ActionMonitor monitor)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 */ start()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 */ schedule(final int requestCode, final long delayMs)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 */ markStart()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 */ markBeginExecute()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 */ markEndExecute(final Object result)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 */ markBackgroundWorkStarting()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 */ markBackgroundCompletionQueued()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 */ markBackgroundWorkQueued()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 */ processBackgroundWorkResponse(final Bundle response)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 */ processBackgroundWorkFailure()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 */ getActionIdx()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 */ generateUniqueActionKey(final String baseKey)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 describeContents()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 */ writeActionToParcel(final Parcel parcel, final int flags)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 */ Action(final Parcel in)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