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.content.Intent;
20import android.os.Bundle;
21import android.os.Looper;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.os.Process;
25import android.test.suitebuilder.annotation.MediumTest;
26import android.util.Log;
27
28import com.android.messaging.Factory;
29import com.android.messaging.FakeContext;
30import com.android.messaging.FakeContext.FakeContextHost;
31import com.android.messaging.FakeFactory;
32import com.android.messaging.datamodel.BugleServiceTestCase;
33import com.android.messaging.datamodel.FakeDataModel;
34import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
35import com.android.messaging.datamodel.action.ActionMonitor.ActionStateChangedListener;
36import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker;
37import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker;
38import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil;
39import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader;
40import com.android.messaging.util.WakeLockHelper;
41
42import java.util.ArrayList;
43
44@MediumTest
45public class ActionServiceTest extends BugleServiceTestCase<ActionServiceImpl>
46        implements FakeContextHost, ActionStateChangedListener, ActionCompletedListener {
47    private static final String TAG = "ActionServiceTest";
48
49    @Override
50    public void onActionStateChanged(final Action action, final int state) {
51        mStates.add(state);
52    }
53
54    @Override
55    public void onActionSucceeded(final ActionMonitor monitor,
56            final Action action, final Object data, final Object result) {
57        final TestChatAction test = (TestChatAction) action;
58        assertNotSame(test.dontRelyOnMe, dontRelyOnMe);
59        // This will be true - but only briefly
60        assertEquals(test.dontRelyOnMe, becauseIChange);
61
62        final ResultTracker tracker = (ResultTracker) data;
63        tracker.completionResult = result;
64        synchronized(tracker) {
65            tracker.notifyAll();
66        }
67    }
68
69    @Override
70    public void onActionFailed(final ActionMonitor monitor, final Action action,
71            final Object data, final Object result) {
72        final TestChatAction test = (TestChatAction) action;
73        assertNotSame(test.dontRelyOnMe, dontRelyOnMe);
74        // This will be true - but only briefly
75        assertEquals(test.dontRelyOnMe, becauseIChange);
76
77        final ResultTracker tracker = (ResultTracker) data;
78        tracker.completionResult = result;
79        synchronized(tracker) {
80            tracker.notifyAll();
81        }
82    }
83
84    /**
85     * For a dummy action verify that the service intent is constructed and queued correctly and
86     * that when that intent is processed it actually executes the action.
87     */
88    public void testChatServiceCreatesIntentAndExecutesAction() {
89        final ResultTracker tracker = new ResultTracker();
90
91        final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this);
92        final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter);
93
94        action.dontRelyOnMe = dontRelyOnMe;
95        assertFalse("Expect service initially stopped", mServiceStarted);
96
97        action.start(monitor);
98
99        assertTrue("Expect service started", mServiceStarted);
100
101        final ArrayList<Intent> intents = mContext.extractIntents();
102        assertNotNull(intents);
103        assertEquals("Expect to see 1 server request queued", 1, intents.size());
104        final Intent intent = intents.get(0);
105        assertEquals("Check pid", intent.getIntExtra(WakeLockHelper.EXTRA_CALLING_PID, 0),
106                Process.myPid());
107        assertEquals("Check opcode", intent.getIntExtra(ActionServiceImpl.EXTRA_OP_CODE, 0),
108                ActionServiceImpl.OP_START_ACTION);
109        assertTrue("Check wakelock held", ActionServiceImpl.sWakeLock.isHeld(intent));
110
111        synchronized(tracker) {
112            try {
113                this.startService(intent);
114                // Wait for callback across threads
115                tracker.wait(2000);
116            } catch (final InterruptedException e) {
117                assertTrue("Interrupted waiting for response processing", false);
118            }
119        }
120
121        assertEquals("Expect three states ", mStates.size(), 3);
122        assertEquals("State-0 should be STATE_QUEUED", (int)mStates.get(0),
123                ActionMonitor.STATE_QUEUED);
124        assertEquals("State-1 should be STATE_EXECUTING", (int)mStates.get(1),
125                ActionMonitor.STATE_EXECUTING);
126        assertEquals("State-2 should be STATE_COMPLETE", (int)mStates.get(2),
127                ActionMonitor.STATE_COMPLETE);
128        // TODO: Should find a way to reliably wait, this is a bit of a hack
129        if (ActionServiceImpl.sWakeLock.isHeld(intent)) {
130            Log.d(TAG, "ActionServiceTest: waiting for wakelock release");
131            try {
132                Thread.sleep(100);
133            } catch (final InterruptedException e) {
134            }
135        }
136        assertFalse("Check wakelock released", ActionServiceImpl.sWakeLock.isHeld(intent));
137    }
138
139    StubBackgroundWorker mWorker;
140    FakeContext mContext;
141    StubLoader mLoader;
142    ActionService mService;
143
144    ArrayList<Integer> mStates;
145
146    private static final String parameter = "parameter";
147    private static final Object dontRelyOnMe = "dontRelyOnMe";
148    private static final Object becauseIChange = "becauseIChange";
149    private static final Object executeActionResult = "executeActionResult";
150    private static final Object processResponseResult = "processResponseResult";
151    private static final Object processFailureResult = "processFailureResult";
152
153    public ActionServiceTest() {
154        super(ActionServiceImpl.class);
155    }
156
157    @Override
158    public void setUp() throws Exception {
159        super.setUp();
160        Log.d(TAG, "ChatActionTest setUp");
161
162        sLooper = Looper.myLooper();
163
164        mWorker = new StubBackgroundWorker();
165        mContext = new FakeContext(getContext(), this);
166        FakeFactory.registerWithFakeContext(getContext(),mContext)
167                .withDataModel(new FakeDataModel(mContext)
168                .withBackgroundWorkerForActionService(mWorker)
169                .withActionService(new ActionService())
170                .withConnectivityUtil(new StubConnectivityUtil(mContext)));
171
172        mStates = new ArrayList<Integer>();
173        setContext(Factory.get().getApplicationContext());
174    }
175
176    @Override
177    public String getServiceClassName() {
178        return ActionServiceImpl.class.getName();
179    }
180
181    boolean mServiceStarted = false;
182
183    @Override
184    public void startServiceForStub(final Intent intent) {
185        // Do nothing until later
186        assertFalse(mServiceStarted);
187        mServiceStarted = true;
188    }
189
190    @Override
191    public void onStartCommandForStub(final Intent intent, final int flags, final int startId) {
192        assertTrue(mServiceStarted);
193    }
194
195    private static Looper sLooper;
196    public static void assertRunsOnOtherThread() {
197        assertTrue (Looper.myLooper() != Looper.getMainLooper());
198        assertTrue (Looper.myLooper() != sLooper);
199    }
200
201    public static class TestChatAction extends Action implements Parcelable {
202        public static String RESPONSE_TEST = "response_test";
203        public static String KEY_PARAMETER = "parameter";
204
205        protected TestChatAction(final String key, final String parameter) {
206            super(key);
207            this.actionParameters.putString(KEY_PARAMETER, parameter);
208        }
209
210        transient Object dontRelyOnMe;
211
212        /**
213         * Process the action locally - runs on service thread
214         */
215        @Override
216        protected Object executeAction() {
217            this.dontRelyOnMe = becauseIChange;
218            assertRunsOnOtherThread();
219            return executeActionResult;
220        }
221
222        /**
223         * Process the response from the server - runs on service thread
224         */
225        @Override
226        protected Object processBackgroundResponse(final Bundle response) {
227            assertRunsOnOtherThread();
228            return processResponseResult;
229        }
230
231        /**
232         * Called in case of failures when sending requests - runs on service thread
233         */
234        @Override
235        protected Object processBackgroundFailure() {
236            assertRunsOnOtherThread();
237            return processFailureResult;
238        }
239
240        private TestChatAction(final Parcel in) {
241            super(in);
242        }
243
244        public static final Parcelable.Creator<TestChatAction> CREATOR
245                = new Parcelable.Creator<TestChatAction>() {
246            @Override
247            public TestChatAction createFromParcel(final Parcel in) {
248                return new TestChatAction(in);
249            }
250
251            @Override
252            public TestChatAction[] newArray(final int size) {
253                return new TestChatAction[size];
254            }
255        };
256
257        @Override
258        public void writeToParcel(final Parcel parcel, final int flags) {
259            writeActionToParcel(parcel, flags);
260        }
261    }
262
263    /**
264     * An operation that notifies a listener upon state changes, execution and completion
265     */
266    public static class TestChatActionMonitor extends ActionMonitor {
267        public TestChatActionMonitor(final String baseKey, final Object data,
268                final ActionStateChangedListener listener, final ActionCompletedListener executed) {
269            super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data);
270            setStateChangedListener(listener);
271            setCompletedListener(executed);
272            assertEquals("Initial state should be STATE_CREATED", mState, STATE_CREATED);
273        }
274    }
275}
276