|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko; |
|
6 |
|
7 import java.util.concurrent.BlockingQueue; |
|
8 import java.util.concurrent.LinkedBlockingQueue; |
|
9 import java.util.concurrent.TimeUnit; |
|
10 |
|
11 import org.json.JSONObject; |
|
12 import org.mozilla.gecko.FennecNativeDriver.LogLevel; |
|
13 import org.mozilla.gecko.gfx.LayerView; |
|
14 import org.mozilla.gecko.gfx.LayerView.DrawListener; |
|
15 import org.mozilla.gecko.mozglue.GeckoLoader; |
|
16 import org.mozilla.gecko.sqlite.SQLiteBridge; |
|
17 import org.mozilla.gecko.util.GeckoEventListener; |
|
18 |
|
19 import android.app.Activity; |
|
20 import android.app.Instrumentation; |
|
21 import android.database.Cursor; |
|
22 import android.os.SystemClock; |
|
23 import android.text.TextUtils; |
|
24 import android.view.KeyEvent; |
|
25 |
|
26 import com.jayway.android.robotium.solo.Solo; |
|
27 |
|
28 public class FennecNativeActions implements Actions { |
|
29 private static final String LOGTAG = "FennecNativeActions"; |
|
30 |
|
31 private Solo mSolo; |
|
32 private Instrumentation mInstr; |
|
33 private Assert mAsserter; |
|
34 |
|
35 public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation, Assert asserter) { |
|
36 mSolo = robocop; |
|
37 mInstr = instrumentation; |
|
38 mAsserter = asserter; |
|
39 |
|
40 GeckoLoader.loadSQLiteLibs(activity, activity.getApplication().getPackageResourcePath()); |
|
41 } |
|
42 |
|
43 class GeckoEventExpecter implements RepeatedEventExpecter { |
|
44 private static final int MAX_WAIT_MS = 90000; |
|
45 |
|
46 private volatile boolean mIsRegistered; |
|
47 |
|
48 private final String mGeckoEvent; |
|
49 private final GeckoEventListener mListener; |
|
50 |
|
51 private volatile boolean mEventEverReceived; |
|
52 private String mEventData; |
|
53 private BlockingQueue<String> mEventDataQueue; |
|
54 |
|
55 GeckoEventExpecter(final String geckoEvent) { |
|
56 if (TextUtils.isEmpty(geckoEvent)) { |
|
57 throw new IllegalArgumentException("geckoEvent must not be empty"); |
|
58 } |
|
59 |
|
60 mGeckoEvent = geckoEvent; |
|
61 mEventDataQueue = new LinkedBlockingQueue<String>(); |
|
62 |
|
63 final GeckoEventExpecter expecter = this; |
|
64 mListener = new GeckoEventListener() { |
|
65 @Override |
|
66 public void handleMessage(final String event, final JSONObject message) { |
|
67 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, |
|
68 "handleMessage called for: " + event + "; expecting: " + mGeckoEvent); |
|
69 mAsserter.is(event, mGeckoEvent, "Given message occurred for registered event: " + message); |
|
70 |
|
71 expecter.notifyOfEvent(message); |
|
72 } |
|
73 }; |
|
74 |
|
75 GeckoAppShell.registerEventListener(mGeckoEvent, mListener); |
|
76 mIsRegistered = true; |
|
77 } |
|
78 |
|
79 public void blockForEvent() { |
|
80 blockForEvent(MAX_WAIT_MS, true); |
|
81 } |
|
82 |
|
83 public void blockForEvent(long millis, boolean failOnTimeout) { |
|
84 if (!mIsRegistered) { |
|
85 throw new IllegalStateException("listener not registered"); |
|
86 } |
|
87 |
|
88 try { |
|
89 mEventData = mEventDataQueue.poll(millis, TimeUnit.MILLISECONDS); |
|
90 } catch (InterruptedException ie) { |
|
91 FennecNativeDriver.log(LogLevel.ERROR, ie); |
|
92 } |
|
93 if (mEventData == null) { |
|
94 if (failOnTimeout) { |
|
95 FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); |
|
96 mAsserter.ok(false, "GeckoEventExpecter", |
|
97 "blockForEvent timeout: "+mGeckoEvent); |
|
98 } else { |
|
99 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, |
|
100 "blockForEvent timeout: "+mGeckoEvent); |
|
101 } |
|
102 } else { |
|
103 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, |
|
104 "unblocked on expecter for " + mGeckoEvent); |
|
105 } |
|
106 } |
|
107 |
|
108 public void blockUntilClear(long millis) { |
|
109 if (!mIsRegistered) { |
|
110 throw new IllegalStateException("listener not registered"); |
|
111 } |
|
112 if (millis <= 0) { |
|
113 throw new IllegalArgumentException("millis must be > 0"); |
|
114 } |
|
115 |
|
116 // wait for at least one event |
|
117 try { |
|
118 mEventData = mEventDataQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS); |
|
119 } catch (InterruptedException ie) { |
|
120 FennecNativeDriver.log(LogLevel.ERROR, ie); |
|
121 } |
|
122 if (mEventData == null) { |
|
123 FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); |
|
124 mAsserter.ok(false, "GeckoEventExpecter", "blockUntilClear timeout"); |
|
125 return; |
|
126 } |
|
127 // now wait for a period of millis where we don't get an event |
|
128 while (true) { |
|
129 try { |
|
130 mEventData = mEventDataQueue.poll(millis, TimeUnit.MILLISECONDS); |
|
131 } catch (InterruptedException ie) { |
|
132 FennecNativeDriver.log(LogLevel.INFO, ie); |
|
133 } |
|
134 if (mEventData == null) { |
|
135 // success |
|
136 break; |
|
137 } |
|
138 } |
|
139 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, |
|
140 "unblocked on expecter for " + mGeckoEvent); |
|
141 } |
|
142 |
|
143 public String blockForEventData() { |
|
144 blockForEvent(); |
|
145 return mEventData; |
|
146 } |
|
147 |
|
148 public String blockForEventDataWithTimeout(long millis) { |
|
149 blockForEvent(millis, false); |
|
150 return mEventData; |
|
151 } |
|
152 |
|
153 public void unregisterListener() { |
|
154 if (!mIsRegistered) { |
|
155 throw new IllegalStateException("listener not registered"); |
|
156 } |
|
157 |
|
158 FennecNativeDriver.log(LogLevel.INFO, |
|
159 "EventExpecter: no longer listening for " + mGeckoEvent); |
|
160 |
|
161 GeckoAppShell.unregisterEventListener(mGeckoEvent, mListener); |
|
162 mIsRegistered = false; |
|
163 } |
|
164 |
|
165 public boolean eventReceived() { |
|
166 return mEventEverReceived; |
|
167 } |
|
168 |
|
169 void notifyOfEvent(final JSONObject message) { |
|
170 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, |
|
171 "received event " + mGeckoEvent); |
|
172 |
|
173 mEventEverReceived = true; |
|
174 |
|
175 try { |
|
176 mEventDataQueue.put(message.toString()); |
|
177 } catch (InterruptedException e) { |
|
178 FennecNativeDriver.log(LogLevel.ERROR, |
|
179 "EventExpecter dropped event: " + message.toString(), e); |
|
180 } |
|
181 } |
|
182 } |
|
183 |
|
184 public RepeatedEventExpecter expectGeckoEvent(final String geckoEvent) { |
|
185 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent); |
|
186 return new GeckoEventExpecter(geckoEvent); |
|
187 } |
|
188 |
|
189 public void sendGeckoEvent(final String geckoEvent, final String data) { |
|
190 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(geckoEvent, data)); |
|
191 } |
|
192 |
|
193 public void sendPreferencesGetEvent(int requestId, String[] prefNames) { |
|
194 GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames)); |
|
195 } |
|
196 |
|
197 public void sendPreferencesObserveEvent(int requestId, String[] prefNames) { |
|
198 GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames)); |
|
199 } |
|
200 |
|
201 public void sendPreferencesRemoveObserversEvent(int requestId) { |
|
202 GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId)); |
|
203 } |
|
204 |
|
205 class PaintExpecter implements RepeatedEventExpecter { |
|
206 private static final int MAX_WAIT_MS = 90000; |
|
207 |
|
208 private boolean mPaintDone; |
|
209 private boolean mListening; |
|
210 |
|
211 private final LayerView mLayerView; |
|
212 private final DrawListener mDrawListener; |
|
213 |
|
214 PaintExpecter() { |
|
215 final PaintExpecter expecter = this; |
|
216 mLayerView = GeckoAppShell.getLayerView(); |
|
217 mDrawListener = new DrawListener() { |
|
218 @Override |
|
219 public void drawFinished() { |
|
220 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, |
|
221 "Received drawFinished notification"); |
|
222 expecter.notifyOfEvent(); |
|
223 } |
|
224 }; |
|
225 mLayerView.addDrawListener(mDrawListener); |
|
226 mListening = true; |
|
227 } |
|
228 |
|
229 private synchronized void notifyOfEvent() { |
|
230 mPaintDone = true; |
|
231 this.notifyAll(); |
|
232 } |
|
233 |
|
234 public synchronized void blockForEvent(long millis, boolean failOnTimeout) { |
|
235 if (!mListening) { |
|
236 throw new IllegalStateException("draw listener not registered"); |
|
237 } |
|
238 long startTime = SystemClock.uptimeMillis(); |
|
239 long endTime = 0; |
|
240 while (!mPaintDone) { |
|
241 try { |
|
242 this.wait(millis); |
|
243 } catch (InterruptedException ie) { |
|
244 FennecNativeDriver.log(LogLevel.ERROR, ie); |
|
245 break; |
|
246 } |
|
247 endTime = SystemClock.uptimeMillis(); |
|
248 if (!mPaintDone && (endTime - startTime >= millis)) { |
|
249 if (failOnTimeout) { |
|
250 FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); |
|
251 mAsserter.ok(false, "PaintExpecter", "blockForEvent timeout"); |
|
252 } |
|
253 return; |
|
254 } |
|
255 } |
|
256 } |
|
257 |
|
258 public synchronized void blockForEvent() { |
|
259 blockForEvent(MAX_WAIT_MS, true); |
|
260 } |
|
261 |
|
262 public synchronized String blockForEventData() { |
|
263 blockForEvent(); |
|
264 return null; |
|
265 } |
|
266 |
|
267 public synchronized String blockForEventDataWithTimeout(long millis) { |
|
268 blockForEvent(millis, false); |
|
269 return null; |
|
270 } |
|
271 |
|
272 public synchronized boolean eventReceived() { |
|
273 return mPaintDone; |
|
274 } |
|
275 |
|
276 public synchronized void blockUntilClear(long millis) { |
|
277 if (!mListening) { |
|
278 throw new IllegalStateException("draw listener not registered"); |
|
279 } |
|
280 if (millis <= 0) { |
|
281 throw new IllegalArgumentException("millis must be > 0"); |
|
282 } |
|
283 // wait for at least one event |
|
284 long startTime = SystemClock.uptimeMillis(); |
|
285 long endTime = 0; |
|
286 while (!mPaintDone) { |
|
287 try { |
|
288 this.wait(MAX_WAIT_MS); |
|
289 } catch (InterruptedException ie) { |
|
290 FennecNativeDriver.log(LogLevel.ERROR, ie); |
|
291 break; |
|
292 } |
|
293 endTime = SystemClock.uptimeMillis(); |
|
294 if (!mPaintDone && (endTime - startTime >= MAX_WAIT_MS)) { |
|
295 FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); |
|
296 mAsserter.ok(false, "PaintExpecter", "blockUtilClear timeout"); |
|
297 return; |
|
298 } |
|
299 } |
|
300 // now wait for a period of millis where we don't get an event |
|
301 startTime = SystemClock.uptimeMillis(); |
|
302 while (true) { |
|
303 try { |
|
304 this.wait(millis); |
|
305 } catch (InterruptedException ie) { |
|
306 FennecNativeDriver.log(LogLevel.ERROR, ie); |
|
307 break; |
|
308 } |
|
309 endTime = SystemClock.uptimeMillis(); |
|
310 if (endTime - startTime >= millis) { |
|
311 // success |
|
312 break; |
|
313 } |
|
314 |
|
315 // we got a notify() before we could wait long enough, so we need to start over |
|
316 // Note, moving the goal post might have us race against a "drawFinished" flood |
|
317 startTime = endTime; |
|
318 } |
|
319 } |
|
320 |
|
321 public synchronized void unregisterListener() { |
|
322 if (!mListening) { |
|
323 throw new IllegalStateException("listener not registered"); |
|
324 } |
|
325 |
|
326 FennecNativeDriver.log(LogLevel.INFO, |
|
327 "PaintExpecter: no longer listening for events"); |
|
328 mLayerView.removeDrawListener(mDrawListener); |
|
329 mListening = false; |
|
330 } |
|
331 } |
|
332 |
|
333 public RepeatedEventExpecter expectPaint() { |
|
334 return new PaintExpecter(); |
|
335 } |
|
336 |
|
337 public void sendSpecialKey(SpecialKey button) { |
|
338 switch(button) { |
|
339 case DOWN: |
|
340 sendKeyCode(KeyEvent.KEYCODE_DPAD_DOWN); |
|
341 break; |
|
342 case UP: |
|
343 sendKeyCode(KeyEvent.KEYCODE_DPAD_UP); |
|
344 break; |
|
345 case LEFT: |
|
346 sendKeyCode(KeyEvent.KEYCODE_DPAD_LEFT); |
|
347 break; |
|
348 case RIGHT: |
|
349 sendKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT); |
|
350 break; |
|
351 case ENTER: |
|
352 sendKeyCode(KeyEvent.KEYCODE_ENTER); |
|
353 break; |
|
354 case MENU: |
|
355 sendKeyCode(KeyEvent.KEYCODE_MENU); |
|
356 break; |
|
357 case BACK: |
|
358 sendKeyCode(KeyEvent.KEYCODE_BACK); |
|
359 break; |
|
360 default: |
|
361 mAsserter.ok(false, "sendSpecialKey", "Unknown SpecialKey " + button); |
|
362 break; |
|
363 } |
|
364 } |
|
365 |
|
366 public void sendKeyCode(int keyCode) { |
|
367 if (keyCode <= 0 || keyCode > KeyEvent.getMaxKeyCode()) { |
|
368 mAsserter.ok(false, "sendKeyCode", "Unknown keyCode " + keyCode); |
|
369 } |
|
370 mInstr.sendCharacterSync(keyCode); |
|
371 } |
|
372 |
|
373 @Override |
|
374 public void sendKeys(String input) { |
|
375 mInstr.sendStringSync(input); |
|
376 } |
|
377 |
|
378 public void drag(int startingX, int endingX, int startingY, int endingY) { |
|
379 mSolo.drag(startingX, endingX, startingY, endingY, 10); |
|
380 } |
|
381 |
|
382 public Cursor querySql(final String dbPath, final String sql) { |
|
383 return new SQLiteBridge(dbPath).rawQuery(sql, null); |
|
384 } |
|
385 } |