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.content.Intent;
20 import android.os.Bundle;
21 import android.os.Looper;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.os.Process;
25 import android.test.suitebuilder.annotation.MediumTest;
26 import android.util.Log;
27 
28 import com.android.messaging.Factory;
29 import com.android.messaging.FakeContext;
30 import com.android.messaging.FakeContext.FakeContextHost;
31 import com.android.messaging.FakeFactory;
32 import com.android.messaging.datamodel.BugleServiceTestCase;
33 import com.android.messaging.datamodel.FakeDataModel;
34 import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
35 import com.android.messaging.datamodel.action.ActionMonitor.ActionStateChangedListener;
36 import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker;
37 import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker;
38 import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil;
39 import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader;
40 import com.android.messaging.util.WakeLockHelper;
41 
42 import java.util.ArrayList;
43 
44 @MediumTest
45 public class ActionServiceTest extends BugleServiceTestCase<ActionServiceImpl>
46         implements FakeContextHost, ActionStateChangedListener, ActionCompletedListener {
47     private static final String TAG = "ActionServiceTest";
48 
49     @Override
onActionStateChanged(final Action action, final int state)50     public void onActionStateChanged(final Action action, final int state) {
51         mStates.add(state);
52     }
53 
54     @Override
onActionSucceeded(final ActionMonitor monitor, final Action action, final Object data, final Object result)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
onActionFailed(final ActionMonitor monitor, final Action action, final Object data, final Object result)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      */
testChatServiceCreatesIntentAndExecutesAction()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 
ActionServiceTest()153     public ActionServiceTest() {
154         super(ActionServiceImpl.class);
155     }
156 
157     @Override
setUp()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
getServiceClassName()177     public String getServiceClassName() {
178         return ActionServiceImpl.class.getName();
179     }
180 
181     boolean mServiceStarted = false;
182 
183     @Override
startServiceForStub(final Intent intent)184     public void startServiceForStub(final Intent intent) {
185         // Do nothing until later
186         assertFalse(mServiceStarted);
187         mServiceStarted = true;
188     }
189 
190     @Override
onStartCommandForStub(final Intent intent, final int flags, final int startId)191     public void onStartCommandForStub(final Intent intent, final int flags, final int startId) {
192         assertTrue(mServiceStarted);
193     }
194 
195     private static Looper sLooper;
assertRunsOnOtherThread()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 
TestChatAction(final String key, final String parameter)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
executeAction()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
processBackgroundResponse(final Bundle response)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
processBackgroundFailure()235         protected Object processBackgroundFailure() {
236             assertRunsOnOtherThread();
237             return processFailureResult;
238         }
239 
TestChatAction(final Parcel in)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
writeToParcel(final Parcel parcel, final int flags)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 {
TestChatActionMonitor(final String baseKey, final Object data, final ActionStateChangedListener listener, final ActionCompletedListener executed)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