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