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
17package com.android.messaging.datamodel.action;
18
19import android.app.IntentService;
20import android.content.Context;
21import android.content.Intent;
22import android.os.Bundle;
23
24import com.android.messaging.Factory;
25import com.android.messaging.datamodel.DataModel;
26import com.android.messaging.datamodel.DataModelException;
27import com.android.messaging.util.Assert;
28import com.android.messaging.util.LogUtil;
29import com.android.messaging.util.LoggingTimer;
30import com.android.messaging.util.WakeLockHelper;
31import com.google.common.annotations.VisibleForTesting;
32
33import 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 */
40public class BackgroundWorkerService extends IntentService {
41    private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
42    private static final boolean VERBOSE = false;
43
44    private static final String WAKELOCK_ID = "bugle_background_worker_wakelock";
45    @VisibleForTesting
46    static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID);
47
48    private final ActionService mHost;
49
50    public BackgroundWorkerService() {
51        super("BackgroundWorker");
52        mHost = DataModel.get().getActionService();
53    }
54
55    /**
56     * Queue a list of requests from action service to this worker
57     */
58    public static void queueBackgroundWork(final List<Action> actions) {
59        for (final Action action : actions) {
60            startServiceWithAction(action, 0);
61        }
62    }
63
64    // ops
65    @VisibleForTesting
66    protected static final int OP_PROCESS_REQUEST = 400;
67
68    // extras
69    @VisibleForTesting
70    protected static final String EXTRA_OP_CODE = "op";
71    @VisibleForTesting
72    protected static final String EXTRA_ACTION = "action";
73    @VisibleForTesting
74    protected static final String EXTRA_ATTEMPT = "retry_attempt";
75
76    /**
77     * Queue action intent to the BackgroundWorkerService after acquiring wake lock
78     */
79    private static void startServiceWithAction(final Action action,
80            final int retryCount) {
81        final Intent intent = new Intent();
82        intent.putExtra(EXTRA_ACTION, action);
83        intent.putExtra(EXTRA_ATTEMPT, retryCount);
84        startServiceWithIntent(OP_PROCESS_REQUEST, intent);
85    }
86
87    /**
88     * Queue intent to the BackgroundWorkerService after acquiring wake lock
89     */
90    private static void startServiceWithIntent(final int opcode, final Intent intent) {
91        final Context context = Factory.get().getApplicationContext();
92
93        intent.setClass(context, BackgroundWorkerService.class);
94        intent.putExtra(EXTRA_OP_CODE, opcode);
95        sWakeLock.acquire(context, intent, opcode);
96        if (VERBOSE) {
97            LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode);
98        }
99
100        if (context.startService(intent) == null) {
101            LogUtil.e(TAG,
102                    "BackgroundWorkerService.startServiceWithAction: failed to start service for "
103                    + opcode);
104            sWakeLock.release(intent, opcode);
105        }
106    }
107
108    @Override
109    protected void onHandleIntent(final Intent intent) {
110        if (intent == null) {
111            // Shouldn't happen but sometimes does following another crash.
112            LogUtil.w(TAG, "BackgroundWorkerService.onHandleIntent: Called with null intent");
113            return;
114        }
115        final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
116        sWakeLock.ensure(intent, opcode);
117
118        try {
119            switch(opcode) {
120                case OP_PROCESS_REQUEST: {
121                    final Action action = intent.getParcelableExtra(EXTRA_ACTION);
122                    final int attempt = intent.getIntExtra(EXTRA_ATTEMPT, -1);
123                    doBackgroundWork(action, attempt);
124                    break;
125                }
126
127                default:
128                    throw new RuntimeException("Unrecognized opcode in BackgroundWorkerService");
129            }
130        } finally {
131            sWakeLock.release(intent, opcode);
132        }
133    }
134
135    /**
136     * Local execution of background work for action on ActionService thread
137     */
138    private void doBackgroundWork(final Action action, final int attempt) {
139        action.markBackgroundWorkStarting();
140        Bundle response = null;
141        try {
142            final LoggingTimer timer = new LoggingTimer(
143                    TAG, action.getClass().getSimpleName() + "#doBackgroundWork");
144            timer.start();
145
146            response = action.doBackgroundWork();
147
148            timer.stopAndLog();
149            action.markBackgroundCompletionQueued();
150            mHost.handleResponseFromBackgroundWorker(action, response);
151        } catch (final Exception exception) {
152            final boolean retry = false;
153            LogUtil.e(TAG, "Error in background worker", exception);
154            if (!(exception instanceof DataModelException)) {
155                // DataModelException is expected (sort-of) and handled in handleFailureFromWorker
156                // below, but other exceptions should crash ENG builds
157                Assert.fail("Unexpected error in background worker - abort");
158            }
159            if (retry) {
160                action.markBackgroundWorkQueued();
161                startServiceWithAction(action, attempt + 1);
162            } else {
163                action.markBackgroundCompletionQueued();
164                mHost.handleFailureFromBackgroundWorker(action, exception);
165            }
166        }
167    }
168}
169