• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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