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