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