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.IntentService; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Bundle; 23 24 import com.android.messaging.Factory; 25 import com.android.messaging.datamodel.DataModel; 26 import com.android.messaging.datamodel.DataModelException; 27 import com.android.messaging.util.Assert; 28 import com.android.messaging.util.LogUtil; 29 import com.android.messaging.util.LoggingTimer; 30 import com.android.messaging.util.WakeLockHelper; 31 import com.google.common.annotations.VisibleForTesting; 32 33 import java.util.List; 34 35 /** 36 * Background worker service is an initial example of a background work queue handler 37 * Used to actually "send" messages which may take some time and should not block ActionService 38 * or UI 39 */ 40 public class BackgroundWorkerService extends IntentService { 41 private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; 42 private static final boolean VERBOSE = false; 43 44 private static final String WAKELOCK_ID = "bugle_background_worker_wakelock"; 45 @VisibleForTesting 46 static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID); 47 48 private final ActionService mHost; 49 BackgroundWorkerService()50 public BackgroundWorkerService() { 51 super("BackgroundWorker"); 52 mHost = DataModel.get().getActionService(); 53 } 54 55 /** 56 * Queue a list of requests from action service to this worker 57 */ queueBackgroundWork(final List<Action> actions)58 public static void queueBackgroundWork(final List<Action> actions) { 59 for (final Action action : actions) { 60 startServiceWithAction(action, 0); 61 } 62 } 63 64 // ops 65 @VisibleForTesting 66 protected static final int OP_PROCESS_REQUEST = 400; 67 68 // extras 69 @VisibleForTesting 70 protected static final String EXTRA_OP_CODE = "op"; 71 @VisibleForTesting 72 protected static final String EXTRA_ACTION = "action"; 73 @VisibleForTesting 74 protected static final String EXTRA_ATTEMPT = "retry_attempt"; 75 76 /** 77 * Queue action intent to the BackgroundWorkerService after acquiring wake lock 78 */ startServiceWithAction(final Action action, final int retryCount)79 private static void startServiceWithAction(final Action action, 80 final int retryCount) { 81 final Intent intent = new Intent(); 82 intent.putExtra(EXTRA_ACTION, action); 83 intent.putExtra(EXTRA_ATTEMPT, retryCount); 84 startServiceWithIntent(OP_PROCESS_REQUEST, intent); 85 } 86 87 /** 88 * Queue intent to the BackgroundWorkerService after acquiring wake lock 89 */ startServiceWithIntent(final int opcode, final Intent intent)90 private static void startServiceWithIntent(final int opcode, final Intent intent) { 91 final Context context = Factory.get().getApplicationContext(); 92 93 intent.setClass(context, BackgroundWorkerService.class); 94 intent.putExtra(EXTRA_OP_CODE, opcode); 95 sWakeLock.acquire(context, intent, opcode); 96 if (VERBOSE) { 97 LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode); 98 } 99 100 if (context.startService(intent) == null) { 101 LogUtil.e(TAG, 102 "BackgroundWorkerService.startServiceWithAction: failed to start service for " 103 + opcode); 104 sWakeLock.release(intent, opcode); 105 } 106 } 107 108 @Override onHandleIntent(final Intent intent)109 protected void onHandleIntent(final Intent intent) { 110 if (intent == null) { 111 // Shouldn't happen but sometimes does following another crash. 112 LogUtil.w(TAG, "BackgroundWorkerService.onHandleIntent: Called with null intent"); 113 return; 114 } 115 final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0); 116 sWakeLock.ensure(intent, opcode); 117 118 try { 119 switch(opcode) { 120 case OP_PROCESS_REQUEST: { 121 final Action action = intent.getParcelableExtra(EXTRA_ACTION); 122 final int attempt = intent.getIntExtra(EXTRA_ATTEMPT, -1); 123 doBackgroundWork(action, attempt); 124 break; 125 } 126 127 default: 128 throw new RuntimeException("Unrecognized opcode in BackgroundWorkerService"); 129 } 130 } finally { 131 sWakeLock.release(intent, opcode); 132 } 133 } 134 135 /** 136 * Local execution of background work for action on ActionService thread 137 */ doBackgroundWork(final Action action, final int attempt)138 private void doBackgroundWork(final Action action, final int attempt) { 139 action.markBackgroundWorkStarting(); 140 Bundle response = null; 141 try { 142 final LoggingTimer timer = new LoggingTimer( 143 TAG, action.getClass().getSimpleName() + "#doBackgroundWork"); 144 timer.start(); 145 146 response = action.doBackgroundWork(); 147 148 timer.stopAndLog(); 149 action.markBackgroundCompletionQueued(); 150 mHost.handleResponseFromBackgroundWorker(action, response); 151 } catch (final Exception exception) { 152 final boolean retry = false; 153 LogUtil.e(TAG, "Error in background worker", exception); 154 if (!(exception instanceof DataModelException)) { 155 // DataModelException is expected (sort-of) and handled in handleFailureFromWorker 156 // below, but other exceptions should crash ENG builds 157 Assert.fail("Unexpected error in background worker - abort"); 158 } 159 if (retry) { 160 action.markBackgroundWorkQueued(); 161 startServiceWithAction(action, attempt + 1); 162 } else { 163 action.markBackgroundCompletionQueued(); 164 mHost.handleFailureFromBackgroundWorker(action, exception); 165 } 166 } 167 } 168 } 169