blob: 6725dde2a1da5a47457febd028103fb16df2b1a6 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.datamodel.action;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import androidx.core.app.JobIntentService;
import com.android.messaging.Factory;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.util.ConnectivityUtil;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.LoggingTimer;
import com.google.common.annotations.VisibleForTesting;
/**
* ActionService used to perform background processing for data model
*/
public class ActionServiceImpl extends JobIntentService {
private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
private static final boolean VERBOSE = false;
/**
* Unique job ID for this service.
*/
public static final int JOB_ID = 1000;
public ActionServiceImpl() {
super();
}
/**
* Start action by sending intent to the service
* @param action - action to start
*/
protected static void startAction(final Action action) {
final Intent intent = makeIntent(OP_START_ACTION);
final Bundle actionBundle = new Bundle();
actionBundle.putParcelable(BUNDLE_ACTION, action);
intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
action.markStart();
startServiceWithIntent(intent);
}
/**
* Schedule an action to run after specified delay using alarm manager to send pendingintent
* @param action - action to start
* @param requestCode - request code used to collapse requests
* @param delayMs - delay in ms (from now) before action will start
*/
protected static void scheduleAction(final Action action, final int requestCode,
final long delayMs) {
final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION);
final Bundle actionBundle = new Bundle();
actionBundle.putParcelable(BUNDLE_ACTION, action);
intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
PendingActionReceiver.scheduleAlarm(intent, requestCode, delayMs);
}
/**
* Handle response returned by BackgroundWorker
* @param request - request generating response
* @param response - response from service
*/
protected static void handleResponseFromBackgroundWorker(final Action action,
final Bundle response) {
final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_RESPONSE);
final Bundle actionBundle = new Bundle();
actionBundle.putParcelable(BUNDLE_ACTION, action);
intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
intent.putExtra(EXTRA_WORKER_RESPONSE, response);
startServiceWithIntent(intent);
}
/**
* Handle response returned by BackgroundWorker
* @param request - request generating failure
*/
protected static void handleFailureFromBackgroundWorker(final Action action,
final Exception exception) {
final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_FAILURE);
final Bundle actionBundle = new Bundle();
actionBundle.putParcelable(BUNDLE_ACTION, action);
intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
intent.putExtra(EXTRA_WORKER_EXCEPTION, exception);
startServiceWithIntent(intent);
}
// ops
@VisibleForTesting
protected static final int OP_START_ACTION = 200;
@VisibleForTesting
protected static final int OP_RECEIVE_BACKGROUND_RESPONSE = 201;
@VisibleForTesting
protected static final int OP_RECEIVE_BACKGROUND_FAILURE = 202;
// extras
@VisibleForTesting
protected static final String EXTRA_OP_CODE = "op";
@VisibleForTesting
protected static final String EXTRA_ACTION_BUNDLE = "datamodel_action_bundle";
@VisibleForTesting
protected static final String EXTRA_WORKER_EXCEPTION = "worker_exception";
@VisibleForTesting
protected static final String EXTRA_WORKER_RESPONSE = "worker_response";
@VisibleForTesting
protected static final String EXTRA_WORKER_UPDATE = "worker_update";
@VisibleForTesting
protected static final String BUNDLE_ACTION = "bundle_action";
private BackgroundWorker mBackgroundWorker;
private ConnectivityUtil mConnectivityUtil;
/**
* Allocate an intent with a specific opcode.
*/
private static Intent makeIntent(final int opcode) {
final Intent intent = new Intent(Factory.get().getApplicationContext(),
ActionServiceImpl.class);
intent.putExtra(EXTRA_OP_CODE, opcode);
return intent;
}
/**
* Broadcast receiver for alarms scheduled through ActionService.
*/
public static class PendingActionReceiver extends BroadcastReceiver {
static final String ACTION = "com.android.messaging.datamodel.PENDING_ACTION";
/**
* Allocate an intent with a specific opcode and alarm action.
*/
public static Intent makeIntent(final int opcode) {
final Intent intent = new Intent(Factory.get().getApplicationContext(),
PendingActionReceiver.class);
intent.setAction(ACTION);
intent.putExtra(EXTRA_OP_CODE, opcode);
return intent;
}
public static void scheduleAlarm(final Intent intent, final int requestCode,
final long delayMs) {
final Context context = Factory.get().getApplicationContext();
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
final AlarmManager mgr =
(AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (delayMs < Long.MAX_VALUE) {
mgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delayMs, pendingIntent);
} else {
mgr.cancel(pendingIntent);
}
}
/**
* {@inheritDoc}
*/
@Override
public void onReceive(final Context context, final Intent intent) {
ActionServiceImpl.startServiceWithIntent(intent);
}
}
/**
* Creates a pending intent that will trigger a data model action when the intent is
* triggered
*/
public static PendingIntent makeStartActionPendingIntent(final Context context,
final Action action, final int requestCode, final boolean launchesAnActivity) {
final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION);
final Bundle actionBundle = new Bundle();
actionBundle.putParcelable(BUNDLE_ACTION, action);
intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
if (launchesAnActivity) {
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
}
return PendingIntent.getBroadcast(context, requestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
* {@inheritDoc}
*/
@Override
public void onCreate() {
super.onCreate();
mBackgroundWorker = DataModel.get().getBackgroundWorkerForActionService();
mConnectivityUtil = DataModel.get().getConnectivityUtil();
mConnectivityUtil.registerForSignalStrength();
}
@Override
public void onDestroy() {
super.onDestroy();
mConnectivityUtil.unregisterForSignalStrength();
}
/**
* Queue intent to the ActionService.
*/
private static void startServiceWithIntent(final Intent intent) {
final Context context = Factory.get().getApplicationContext();
final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
intent.setClass(context, ActionServiceImpl.class);
enqueueWork(context, intent);
}
public static void enqueueWork(Context context, Intent work) {
enqueueWork(context, ActionServiceImpl.class, JOB_ID, work);
}
/**
* {@inheritDoc}
*/
@Override
protected void onHandleWork(final Intent intent) {
if (intent == null) {
// Shouldn't happen but sometimes does following another crash.
LogUtil.w(TAG, "ActionService.onHandleIntent: Called with null intent");
return;
}
final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
Action action;
final Bundle actionBundle = intent.getBundleExtra(EXTRA_ACTION_BUNDLE);
actionBundle.setClassLoader(getClassLoader());
switch(opcode) {
case OP_START_ACTION: {
action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
executeAction(action);
break;
}
case OP_RECEIVE_BACKGROUND_RESPONSE: {
action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
final Bundle response = intent.getBundleExtra(EXTRA_WORKER_RESPONSE);
processBackgroundResponse(action, response);
break;
}
case OP_RECEIVE_BACKGROUND_FAILURE: {
action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
processBackgroundFailure(action);
break;
}
default:
throw new RuntimeException("Unrecognized opcode in ActionServiceImpl");
}
action.sendBackgroundActions(mBackgroundWorker);
}
private static final long EXECUTION_TIME_WARN_LIMIT_MS = 1000; // 1 second
/**
* Local execution of action on ActionService thread
*/
private void executeAction(final Action action) {
action.markBeginExecute();
final LoggingTimer timer = createLoggingTimer(action, "#executeAction");
timer.start();
final Object result = action.executeAction();
timer.stopAndLog();
action.markEndExecute(result);
}
/**
* Process response on ActionService thread
*/
private void processBackgroundResponse(final Action action, final Bundle response) {
final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundResponse");
timer.start();
action.processBackgroundWorkResponse(response);
timer.stopAndLog();
}
/**
* Process failure on ActionService thread
*/
private void processBackgroundFailure(final Action action) {
final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundFailure");
timer.start();
action.processBackgroundWorkFailure();
timer.stopAndLog();
}
private static LoggingTimer createLoggingTimer(
final Action action, final String methodName) {
return new LoggingTimer(TAG, action.getClass().getSimpleName() + methodName,
EXECUTION_TIME_WARN_LIMIT_MS);
}
}