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 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