1/*
2 * Copyright (C) 2014 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.google.android.apps.common.testing.ui.espresso.base;
18
19import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException;
20import com.google.android.apps.common.testing.ui.espresso.UiController;
21import com.google.android.apps.common.testing.ui.espresso.util.HumanReadables;
22import com.google.android.apps.common.testing.ui.testapp.R;
23import com.google.android.apps.common.testing.ui.testapp.SendActivity;
24import com.google.common.base.Optional;
25
26import android.app.Activity;
27import android.app.Instrumentation;
28import android.os.Build;
29import android.os.Looper;
30import android.os.SystemClock;
31import android.test.ActivityInstrumentationTestCase2;
32import android.test.suitebuilder.annotation.LargeTest;
33import android.util.Log;
34import android.view.KeyCharacterMap;
35import android.view.KeyEvent;
36import android.view.MotionEvent;
37import android.view.View;
38
39import java.util.concurrent.CountDownLatch;
40import java.util.concurrent.TimeUnit;
41import java.util.concurrent.atomic.AtomicBoolean;
42
43/**
44 * Test for {@link UiControllerImpl}.
45 */
46public class UiControllerImplIntegrationTest
47    extends ActivityInstrumentationTestCase2<SendActivity> {
48  private Activity sendActivity;
49  private final AtomicBoolean injectEventWorked = new AtomicBoolean(false);
50  private final AtomicBoolean injectEventThrewSecurityException = new AtomicBoolean(false);
51  private final CountDownLatch focusLatch = new CountDownLatch(1);
52  private final CountDownLatch latch = new CountDownLatch(1);
53  private UiController uiController;
54
55  @SuppressWarnings("deprecation")
56  public UiControllerImplIntegrationTest() {
57    // Supporting froyo.
58    super("com.google.android.apps.common.testing.ui.testapp", SendActivity.class);
59  }
60
61  @Override
62  public void setUp() throws Exception {
63    super.setUp();
64    EventInjector injector = null;
65    if (Build.VERSION.SDK_INT > 15) {
66      InputManagerEventInjectionStrategy strat = new InputManagerEventInjectionStrategy();
67      strat.initialize();
68      injector = new EventInjector(strat);
69    } else {
70      WindowManagerEventInjectionStrategy strat = new WindowManagerEventInjectionStrategy();
71      strat.initialize();
72      injector = new EventInjector(strat);
73    }
74    uiController = new UiControllerImpl(
75        injector,
76        new AsyncTaskPoolMonitor(new ThreadPoolExecutorExtractor(
77            Looper.getMainLooper()).getAsyncTaskThreadPool()),
78        Optional.<AsyncTaskPoolMonitor>absent(),
79        new IdlingResourceRegistry(Looper.getMainLooper()),
80        Looper.getMainLooper());
81  }
82
83
84  @Override
85  public SendActivity getActivity() {
86    SendActivity a = super.getActivity();
87
88    while (!a.hasWindowFocus()) {
89      getInstrumentation().waitForIdleSync();
90    }
91
92    return a;
93  }
94
95  @LargeTest
96  public void testInjectKeyEvent() throws InterruptedException {
97    sendActivity = getActivity();
98    getInstrumentation().waitForIdleSync();
99
100    getInstrumentation().runOnMainSync(new Runnable() {
101      @Override
102      public void run() {
103        try {
104          KeyCharacterMap keyCharacterMap = UiControllerImpl.getKeyCharacterMap();
105          KeyEvent[] events = keyCharacterMap.getEvents("a".toCharArray());
106          injectEventWorked.set(uiController.injectKeyEvent(events[0]));
107          latch.countDown();
108        } catch (InjectEventSecurityException e) {
109          injectEventThrewSecurityException.set(true);
110        }
111      }
112    });
113
114    assertFalse("injectEvent threw a SecurityException", injectEventThrewSecurityException.get());
115    assertTrue("Timed out!", latch.await(10, TimeUnit.SECONDS));
116    assertTrue(injectEventWorked.get());
117  }
118
119  @LargeTest
120  public void testInjectString() throws InterruptedException {
121    sendActivity = getActivity();
122    getInstrumentation().waitForIdleSync();
123    final AtomicBoolean requestFocusSucceded = new AtomicBoolean(false);
124
125    getInstrumentation().runOnMainSync(new Runnable() {
126      @Override
127      public void run() {
128        final View view = sendActivity.findViewById(R.id.send_data_to_call_edit_text);
129        Log.i("TEST", HumanReadables.describe(view));
130        requestFocusSucceded.set(view.requestFocus() && view.hasWindowFocus());
131        Log.i("TEST-post", HumanReadables.describe(view));
132        focusLatch.countDown();
133      }
134    });
135
136    getInstrumentation().waitForIdleSync();
137    assertTrue("requestFocus timed out!", focusLatch.await(2, TimeUnit.SECONDS));
138    assertTrue("requestFocus failed.", requestFocusSucceded.get());
139
140    getInstrumentation().runOnMainSync(new Runnable() {
141      @Override
142      public void run() {
143        try {
144          injectEventWorked.set(uiController.injectString("Hello! \n&*$$$"));
145          latch.countDown();
146        } catch (InjectEventSecurityException e) {
147          injectEventThrewSecurityException.set(true);
148        }
149      }
150    });
151
152    assertFalse("SecurityException exception was thrown.", injectEventThrewSecurityException.get());
153    assertTrue("Timed out!", latch.await(20, TimeUnit.SECONDS));
154    assertTrue(injectEventWorked.get());
155  }
156
157  @LargeTest
158  public void testInjectLargeString() throws InterruptedException {
159    sendActivity = getActivity();
160    getInstrumentation().waitForIdleSync();
161    final AtomicBoolean requestFocusSucceded = new AtomicBoolean(false);
162
163    getInstrumentation().runOnMainSync(new Runnable() {
164      @Override
165      public void run() {
166        final View view = sendActivity.findViewById(R.id.send_data_to_call_edit_text);
167        Log.i("TEST", HumanReadables.describe(view));
168        requestFocusSucceded.set(view.requestFocus());
169        Log.i("TEST-post", HumanReadables.describe(view));
170
171        focusLatch.countDown();
172      }
173    });
174
175    assertTrue("requestFocus timed out!", focusLatch.await(2, TimeUnit.SECONDS));
176    assertTrue("requestFocus failed.", requestFocusSucceded.get());
177
178    getInstrumentation().runOnMainSync(new Runnable() {
179      @Override
180      public void run() {
181        try {
182          injectEventWorked.set(uiController.injectString("This is a string with 32 chars!!"));
183          latch.countDown();
184        } catch (InjectEventSecurityException e) {
185          injectEventThrewSecurityException.set(true);
186        }
187      }
188    });
189
190    assertFalse("SecurityException exception was thrown.", injectEventThrewSecurityException.get());
191    assertTrue("Timed out!", latch.await(20, TimeUnit.SECONDS));
192    assertTrue(injectEventWorked.get());
193  }
194
195  @LargeTest
196  public void testInjectEmptyString() throws InterruptedException {
197    sendActivity = getActivity();
198    getInstrumentation().waitForIdleSync();
199    final AtomicBoolean requestFocusSucceded = new AtomicBoolean(false);
200
201    getInstrumentation().runOnMainSync(new Runnable() {
202      @Override
203      public void run() {
204        final View view = sendActivity.findViewById(R.id.send_data_to_call_edit_text);
205        requestFocusSucceded.set(view.requestFocus());
206        focusLatch.countDown();
207      }
208    });
209
210    assertTrue("requestFocus timed out!", focusLatch.await(2, TimeUnit.SECONDS));
211    assertTrue("requestFocus failed.", requestFocusSucceded.get());
212
213    getInstrumentation().runOnMainSync(new Runnable() {
214      @Override
215      public void run() {
216        try {
217          injectEventWorked.set(uiController.injectString(""));
218          latch.countDown();
219        } catch (InjectEventSecurityException e) {
220          injectEventThrewSecurityException.set(true);
221        }
222      }
223    });
224
225    assertFalse("SecurityException exception was thrown.", injectEventThrewSecurityException.get());
226    assertTrue("Timed out!", latch.await(20, TimeUnit.SECONDS));
227    assertTrue(injectEventWorked.get());
228  }
229
230  @LargeTest
231  public void testInjectStringSecurityException() throws InterruptedException {
232    getInstrumentation().runOnMainSync(new Runnable() {
233      @Override
234      public void run() {
235        try {
236          injectEventWorked.set(uiController.injectString("Hello! \n&*$$$"));
237          latch.countDown();
238        } catch (InjectEventSecurityException e) {
239          injectEventThrewSecurityException.set(true);
240        }
241      }
242    });
243
244    assertTrue("SecurityException exception was thrown.", injectEventThrewSecurityException.get());
245    assertFalse("Did NOT time out!", latch.await(3, TimeUnit.SECONDS));
246    assertFalse(injectEventWorked.get());
247  }
248
249  @LargeTest
250  public void testInjectMotionEvent() throws InterruptedException {
251    sendActivity = getActivity();
252    final int xy[] = getCoordinatesInMiddleOfSendButton(sendActivity, getInstrumentation());
253
254    getInstrumentation().runOnMainSync(new Runnable() {
255      @Override
256      public void run() {
257        long downTime = SystemClock.uptimeMillis();
258        try {
259          MotionEvent event = MotionEvent.obtain(downTime,
260              SystemClock.uptimeMillis(),
261              MotionEvent.ACTION_DOWN,
262              xy[0],
263              xy[1],
264              0);
265
266          injectEventWorked.set(uiController.injectMotionEvent(event));
267          event.recycle();
268          latch.countDown();
269        } catch (InjectEventSecurityException e) {
270          injectEventThrewSecurityException.set(true);
271        }
272      }
273    });
274
275    assertFalse("SecurityException exception was thrown.", injectEventThrewSecurityException.get());
276    assertTrue("Timed out!", latch.await(10, TimeUnit.SECONDS));
277    assertTrue(injectEventWorked.get());
278  }
279
280  static int[] getCoordinatesInMiddleOfSendButton(
281      Activity activity, Instrumentation instrumentation) {
282    final View sendButton = activity.findViewById(R.id.send_button);
283    final int[] xy = new int[2];
284    instrumentation.runOnMainSync(new Runnable() {
285      @Override
286      public void run() {
287        sendButton.getLocationOnScreen(xy);
288      }
289    });
290    int x = xy[0] + (sendButton.getWidth() / 2);
291    int y = xy[1] + (sendButton.getHeight() / 2);
292    int[] xyMiddle = {x, y};
293    return xyMiddle;
294  }
295}
296