Home | History | Annotate | Download | only in action
      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 
     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      */
     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      */
     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      */
     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      */
     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      */
    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          */
    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 
    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
    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      */
    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
    206     public void onCreate() {
    207         super.onCreate();
    208         mBackgroundWorker = DataModel.get().getBackgroundWorkerForActionService();
    209         DataModel.get().getConnectivityUtil().registerForSignalStrength();
    210     }
    211 
    212     @Override
    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      */
    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
    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      */
    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      */
    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      */
    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 
    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