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.app.AlarmManager; 20 import android.app.IntentService; 21 import android.app.PendingIntent; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.Bundle; 26 import android.os.SystemClock; 27 28 import com.android.messaging.Factory; 29 import com.android.messaging.datamodel.DataModel; 30 import com.android.messaging.util.LogUtil; 31 import com.android.messaging.util.LoggingTimer; 32 import com.android.messaging.util.WakeLockHelper; 33 import com.google.common.annotations.VisibleForTesting; 34 35 /** 36 * ActionService used to perform background processing for data model 37 */ 38 public class ActionServiceImpl extends IntentService { 39 private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; 40 private static final boolean VERBOSE = false; 41 42 public ActionServiceImpl() { 43 super("ActionService"); 44 } 45 46 /** 47 * Start action by sending intent to the service 48 * @param action - action to start 49 */ 50 protected static void startAction(final Action action) { 51 final Intent intent = makeIntent(OP_START_ACTION); 52 final Bundle actionBundle = new Bundle(); 53 actionBundle.putParcelable(BUNDLE_ACTION, action); 54 intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); 55 action.markStart(); 56 startServiceWithIntent(intent); 57 } 58 59 /** 60 * Schedule an action to run after specified delay using alarm manager to send pendingintent 61 * @param action - action to start 62 * @param requestCode - request code used to collapse requests 63 * @param delayMs - delay in ms (from now) before action will start 64 */ 65 protected static void scheduleAction(final Action action, final int requestCode, 66 final long delayMs) { 67 final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION); 68 final Bundle actionBundle = new Bundle(); 69 actionBundle.putParcelable(BUNDLE_ACTION, action); 70 intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); 71 72 PendingActionReceiver.scheduleAlarm(intent, requestCode, delayMs); 73 } 74 75 /** 76 * Handle response returned by BackgroundWorker 77 * @param request - request generating response 78 * @param response - response from service 79 */ 80 protected static void handleResponseFromBackgroundWorker(final Action action, 81 final Bundle response) { 82 final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_RESPONSE); 83 84 final Bundle actionBundle = new Bundle(); 85 actionBundle.putParcelable(BUNDLE_ACTION, action); 86 intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); 87 intent.putExtra(EXTRA_WORKER_RESPONSE, response); 88 89 startServiceWithIntent(intent); 90 } 91 92 /** 93 * Handle response returned by BackgroundWorker 94 * @param request - request generating failure 95 */ 96 protected static void handleFailureFromBackgroundWorker(final Action action, 97 final Exception exception) { 98 final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_FAILURE); 99 100 final Bundle actionBundle = new Bundle(); 101 actionBundle.putParcelable(BUNDLE_ACTION, action); 102 intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); 103 intent.putExtra(EXTRA_WORKER_EXCEPTION, exception); 104 105 startServiceWithIntent(intent); 106 } 107 108 // ops 109 @VisibleForTesting 110 protected static final int OP_START_ACTION = 200; 111 @VisibleForTesting 112 protected static final int OP_RECEIVE_BACKGROUND_RESPONSE = 201; 113 @VisibleForTesting 114 protected static final int OP_RECEIVE_BACKGROUND_FAILURE = 202; 115 116 // extras 117 @VisibleForTesting 118 protected static final String EXTRA_OP_CODE = "op"; 119 @VisibleForTesting 120 protected static final String EXTRA_ACTION_BUNDLE = "datamodel_action_bundle"; 121 @VisibleForTesting 122 protected static final String EXTRA_WORKER_EXCEPTION = "worker_exception"; 123 @VisibleForTesting 124 protected static final String EXTRA_WORKER_RESPONSE = "worker_response"; 125 @VisibleForTesting 126 protected static final String EXTRA_WORKER_UPDATE = "worker_update"; 127 @VisibleForTesting 128 protected static final String BUNDLE_ACTION = "bundle_action"; 129 130 private BackgroundWorker mBackgroundWorker; 131 132 /** 133 * Allocate an intent with a specific opcode. 134 */ 135 private static Intent makeIntent(final int opcode) { 136 final Intent intent = new Intent(Factory.get().getApplicationContext(), 137 ActionServiceImpl.class); 138 intent.putExtra(EXTRA_OP_CODE, opcode); 139 return intent; 140 } 141 142 /** 143 * Broadcast receiver for alarms scheduled through ActionService. 144 */ 145 public static class PendingActionReceiver extends BroadcastReceiver { 146 static final String ACTION = "com.android.messaging.datamodel.PENDING_ACTION"; 147 148 /** 149 * Allocate an intent with a specific opcode and alarm action. 150 */ 151 public static Intent makeIntent(final int opcode) { 152 final Intent intent = new Intent(Factory.get().getApplicationContext(), 153 PendingActionReceiver.class); 154 intent.setAction(ACTION); 155 intent.putExtra(EXTRA_OP_CODE, opcode); 156 return intent; 157 } 158 159 public static void scheduleAlarm(final Intent intent, final int requestCode, 160 final long delayMs) { 161 final Context context = Factory.get().getApplicationContext(); 162 final PendingIntent pendingIntent = PendingIntent.getBroadcast( 163 context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT); 164 165 final AlarmManager mgr = 166 (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 167 168 if (delayMs < Long.MAX_VALUE) { 169 mgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 170 SystemClock.elapsedRealtime() + delayMs, pendingIntent); 171 } else { 172 mgr.cancel(pendingIntent); 173 } 174 } 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override 180 public void onReceive(final Context context, final Intent intent) { 181 ActionServiceImpl.startServiceWithIntent(intent); 182 } 183 } 184 185 /** 186 * Creates a pending intent that will trigger a data model action when the intent is 187 * triggered 188 */ 189 public static PendingIntent makeStartActionPendingIntent(final Context context, 190 final Action action, final int requestCode, final boolean launchesAnActivity) { 191 final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION); 192 final Bundle actionBundle = new Bundle(); 193 actionBundle.putParcelable(BUNDLE_ACTION, action); 194 intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle); 195 if (launchesAnActivity) { 196 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 197 } 198 return PendingIntent.getBroadcast(context, requestCode, intent, 199 PendingIntent.FLAG_UPDATE_CURRENT); 200 } 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override 206 public void onCreate() { 207 super.onCreate(); 208 mBackgroundWorker = DataModel.get().getBackgroundWorkerForActionService(); 209 DataModel.get().getConnectivityUtil().registerForSignalStrength(); 210 } 211 212 @Override 213 public void onDestroy() { 214 super.onDestroy(); 215 DataModel.get().getConnectivityUtil().unregisterForSignalStrength(); 216 } 217 218 private static final String WAKELOCK_ID = "bugle_datamodel_service_wakelock"; 219 @VisibleForTesting 220 static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID); 221 222 /** 223 * Queue intent to the ActionService after acquiring wake lock 224 */ 225 private static void startServiceWithIntent(final Intent intent) { 226 final Context context = Factory.get().getApplicationContext(); 227 final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0); 228 // Increase refCount on wake lock - acquiring if necessary 229 if (VERBOSE) { 230 LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode); 231 } 232 sWakeLock.acquire(context, intent, opcode); 233 intent.setClass(context, ActionServiceImpl.class); 234 235 // TODO: Note that intent will be quietly discarded if it exceeds available rpc 236 // memory (in total around 1MB). See this article for background 237 // http://developer.android.com/reference/android/os/TransactionTooLargeException.html 238 // Perhaps we should keep large structures in the action monitor? 239 if (context.startService(intent) == null) { 240 LogUtil.e(TAG, 241 "ActionService.startServiceWithIntent: failed to start service for intent " 242 + intent); 243 sWakeLock.release(intent, opcode); 244 } 245 } 246 247 /** 248 * {@inheritDoc} 249 */ 250 @Override 251 protected void onHandleIntent(final Intent intent) { 252 if (intent == null) { 253 // Shouldn't happen but sometimes does following another crash. 254 LogUtil.w(TAG, "ActionService.onHandleIntent: Called with null intent"); 255 return; 256 } 257 final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0); 258 sWakeLock.ensure(intent, opcode); 259 260 try { 261 Action action; 262 final Bundle actionBundle = intent.getBundleExtra(EXTRA_ACTION_BUNDLE); 263 actionBundle.setClassLoader(getClassLoader()); 264 switch(opcode) { 265 case OP_START_ACTION: { 266 action = (Action) actionBundle.getParcelable(BUNDLE_ACTION); 267 executeAction(action); 268 break; 269 } 270 271 case OP_RECEIVE_BACKGROUND_RESPONSE: { 272 action = (Action) actionBundle.getParcelable(BUNDLE_ACTION); 273 final Bundle response = intent.getBundleExtra(EXTRA_WORKER_RESPONSE); 274 processBackgroundResponse(action, response); 275 break; 276 } 277 278 case OP_RECEIVE_BACKGROUND_FAILURE: { 279 action = (Action) actionBundle.getParcelable(BUNDLE_ACTION); 280 processBackgroundFailure(action); 281 break; 282 } 283 284 default: 285 throw new RuntimeException("Unrecognized opcode in ActionServiceImpl"); 286 } 287 288 action.sendBackgroundActions(mBackgroundWorker); 289 } finally { 290 // Decrease refCount on wake lock - releasing if necessary 291 sWakeLock.release(intent, opcode); 292 } 293 } 294 295 private static final long EXECUTION_TIME_WARN_LIMIT_MS = 1000; // 1 second 296 /** 297 * Local execution of action on ActionService thread 298 */ 299 private void executeAction(final Action action) { 300 action.markBeginExecute(); 301 302 final LoggingTimer timer = createLoggingTimer(action, "#executeAction"); 303 timer.start(); 304 305 final Object result = action.executeAction(); 306 307 timer.stopAndLog(); 308 309 action.markEndExecute(result); 310 } 311 312 /** 313 * Process response on ActionService thread 314 */ 315 private void processBackgroundResponse(final Action action, final Bundle response) { 316 final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundResponse"); 317 timer.start(); 318 319 action.processBackgroundWorkResponse(response); 320 321 timer.stopAndLog(); 322 } 323 324 /** 325 * Process failure on ActionService thread 326 */ 327 private void processBackgroundFailure(final Action action) { 328 final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundFailure"); 329 timer.start(); 330 331 action.processBackgroundWorkFailure(); 332 333 timer.stopAndLog(); 334 } 335 336 private static LoggingTimer createLoggingTimer( 337 final Action action, final String methodName) { 338 return new LoggingTimer(TAG, action.getClass().getSimpleName() + methodName, 339 EXECUTION_TIME_WARN_LIMIT_MS); 340 } 341 } 342