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