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