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/. */
5 package org
.mozilla
.gecko
;
7 import java
.util
.concurrent
.BlockingQueue
;
8 import java
.util
.concurrent
.LinkedBlockingQueue
;
9 import java
.util
.concurrent
.TimeUnit
;
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
;
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
;
26 import com
.jayway
.android
.robotium
.solo
.Solo
;
28 public class FennecNativeActions
implements Actions
{
29 private static final String LOGTAG
= "FennecNativeActions";
32 private Instrumentation mInstr
;
33 private Assert mAsserter
;
35 public FennecNativeActions(Activity activity
, Solo robocop
, Instrumentation instrumentation
, Assert asserter
) {
37 mInstr
= instrumentation
;
40 GeckoLoader
.loadSQLiteLibs(activity
, activity
.getApplication().getPackageResourcePath());
43 class GeckoEventExpecter
implements RepeatedEventExpecter
{
44 private static final int MAX_WAIT_MS
= 180000;
46 private volatile boolean mIsRegistered
;
48 private final String mGeckoEvent
;
49 private final GeckoEventListener mListener
;
51 private volatile boolean mEventEverReceived
;
52 private String mEventData
;
53 private BlockingQueue
<String
> mEventDataQueue
;
55 GeckoEventExpecter(final String geckoEvent
) {
56 if (TextUtils
.isEmpty(geckoEvent
)) {
57 throw new IllegalArgumentException("geckoEvent must not be empty");
60 mGeckoEvent
= geckoEvent
;
61 mEventDataQueue
= new LinkedBlockingQueue
<String
>();
63 final GeckoEventExpecter expecter
= this;
64 mListener
= new GeckoEventListener() {
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
);
71 expecter
.notifyOfEvent(message
);
75 EventDispatcher
.getInstance().registerGeckoThreadListener(mListener
, mGeckoEvent
);
79 public void blockForEvent() {
80 blockForEvent(MAX_WAIT_MS
, true);
83 public void blockForEvent(long millis
, boolean failOnTimeout
) {
85 throw new IllegalStateException("listener not registered");
89 mEventData
= mEventDataQueue
.poll(millis
, TimeUnit
.MILLISECONDS
);
90 } catch (InterruptedException ie
) {
91 FennecNativeDriver
.log(LogLevel
.ERROR
, ie
);
93 if (mEventData
== null) {
95 FennecNativeDriver
.logAllStackTraces(FennecNativeDriver
.LogLevel
.ERROR
);
96 mAsserter
.ok(false, "GeckoEventExpecter",
97 "blockForEvent timeout: "+mGeckoEvent
);
99 FennecNativeDriver
.log(FennecNativeDriver
.LogLevel
.DEBUG
,
100 "blockForEvent timeout: "+mGeckoEvent
);
103 FennecNativeDriver
.log(FennecNativeDriver
.LogLevel
.DEBUG
,
104 "unblocked on expecter for " + mGeckoEvent
);
108 public void blockUntilClear(long millis
) {
109 if (!mIsRegistered
) {
110 throw new IllegalStateException("listener not registered");
113 throw new IllegalArgumentException("millis must be > 0");
116 // wait for at least one event
118 mEventData
= mEventDataQueue
.poll(MAX_WAIT_MS
, TimeUnit
.MILLISECONDS
);
119 } catch (InterruptedException ie
) {
120 FennecNativeDriver
.log(LogLevel
.ERROR
, ie
);
122 if (mEventData
== null) {
123 FennecNativeDriver
.logAllStackTraces(FennecNativeDriver
.LogLevel
.ERROR
);
124 mAsserter
.ok(false, "GeckoEventExpecter", "blockUntilClear timeout");
127 // now wait for a period of millis where we don't get an event
130 mEventData
= mEventDataQueue
.poll(millis
, TimeUnit
.MILLISECONDS
);
131 } catch (InterruptedException ie
) {
132 FennecNativeDriver
.log(LogLevel
.INFO
, ie
);
134 if (mEventData
== null) {
139 FennecNativeDriver
.log(FennecNativeDriver
.LogLevel
.DEBUG
,
140 "unblocked on expecter for " + mGeckoEvent
);
143 public String
blockForEventData() {
148 public String
blockForEventDataWithTimeout(long millis
) {
149 blockForEvent(millis
, false);
153 public void unregisterListener() {
154 if (!mIsRegistered
) {
155 throw new IllegalStateException("listener not registered");
158 FennecNativeDriver
.log(LogLevel
.INFO
,
159 "EventExpecter: no longer listening for " + mGeckoEvent
);
161 EventDispatcher
.getInstance().unregisterGeckoThreadListener(mListener
, mGeckoEvent
);
162 mIsRegistered
= false;
165 public boolean eventReceived() {
166 return mEventEverReceived
;
169 void notifyOfEvent(final JSONObject message
) {
170 FennecNativeDriver
.log(FennecNativeDriver
.LogLevel
.DEBUG
,
171 "received event " + mGeckoEvent
);
173 mEventEverReceived
= true;
176 mEventDataQueue
.put(message
.toString());
177 } catch (InterruptedException e
) {
178 FennecNativeDriver
.log(LogLevel
.ERROR
,
179 "EventExpecter dropped event: " + message
.toString(), e
);
184 public RepeatedEventExpecter
expectGeckoEvent(final String geckoEvent
) {
185 FennecNativeDriver
.log(FennecNativeDriver
.LogLevel
.DEBUG
, "waiting for " + geckoEvent
);
186 return new GeckoEventExpecter(geckoEvent
);
189 public void sendGeckoEvent(final String geckoEvent
, final String data
) {
190 GeckoAppShell
.sendEventToGecko(GeckoEvent
.createBroadcastEvent(geckoEvent
, data
));
193 public void sendPreferencesGetEvent(int requestId
, String
[] prefNames
) {
194 GeckoAppShell
.sendEventToGecko(GeckoEvent
.createPreferencesGetEvent(requestId
, prefNames
));
197 public void sendPreferencesObserveEvent(int requestId
, String
[] prefNames
) {
198 GeckoAppShell
.sendEventToGecko(GeckoEvent
.createPreferencesObserveEvent(requestId
, prefNames
));
201 public void sendPreferencesRemoveObserversEvent(int requestId
) {
202 GeckoAppShell
.sendEventToGecko(GeckoEvent
.createPreferencesRemoveObserversEvent(requestId
));
205 class PaintExpecter
implements RepeatedEventExpecter
{
206 private static final int MAX_WAIT_MS
= 90000;
208 private boolean mPaintDone
;
209 private boolean mListening
;
211 private final LayerView mLayerView
;
212 private final DrawListener mDrawListener
;
215 final PaintExpecter expecter
= this;
216 mLayerView
= GeckoAppShell
.getLayerView();
217 mDrawListener
= new DrawListener() {
219 public void drawFinished() {
220 FennecNativeDriver
.log(FennecNativeDriver
.LogLevel
.DEBUG
,
221 "Received drawFinished notification");
222 expecter
.notifyOfEvent();
225 mLayerView
.addDrawListener(mDrawListener
);
229 private synchronized void notifyOfEvent() {
234 public synchronized void blockForEvent(long millis
, boolean failOnTimeout
) {
236 throw new IllegalStateException("draw listener not registered");
238 long startTime
= SystemClock
.uptimeMillis();
240 while (!mPaintDone
) {
243 } catch (InterruptedException ie
) {
244 FennecNativeDriver
.log(LogLevel
.ERROR
, ie
);
247 endTime
= SystemClock
.uptimeMillis();
248 if (!mPaintDone
&& (endTime
- startTime
>= millis
)) {
250 FennecNativeDriver
.logAllStackTraces(FennecNativeDriver
.LogLevel
.ERROR
);
251 mAsserter
.ok(false, "PaintExpecter", "blockForEvent timeout");
258 public synchronized void blockForEvent() {
259 blockForEvent(MAX_WAIT_MS
, true);
262 public synchronized String
blockForEventData() {
267 public synchronized String
blockForEventDataWithTimeout(long millis
) {
268 blockForEvent(millis
, false);
272 public synchronized boolean eventReceived() {
276 public synchronized void blockUntilClear(long millis
) {
278 throw new IllegalStateException("draw listener not registered");
281 throw new IllegalArgumentException("millis must be > 0");
283 // wait for at least one event
284 long startTime
= SystemClock
.uptimeMillis();
286 while (!mPaintDone
) {
288 this.wait(MAX_WAIT_MS
);
289 } catch (InterruptedException ie
) {
290 FennecNativeDriver
.log(LogLevel
.ERROR
, ie
);
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");
300 // now wait for a period of millis where we don't get an event
301 startTime
= SystemClock
.uptimeMillis();
305 } catch (InterruptedException ie
) {
306 FennecNativeDriver
.log(LogLevel
.ERROR
, ie
);
309 endTime
= SystemClock
.uptimeMillis();
310 if (endTime
- startTime
>= millis
) {
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
321 public synchronized void unregisterListener() {
323 throw new IllegalStateException("listener not registered");
326 FennecNativeDriver
.log(LogLevel
.INFO
,
327 "PaintExpecter: no longer listening for events");
328 mLayerView
.removeDrawListener(mDrawListener
);
333 public RepeatedEventExpecter
expectPaint() {
334 return new PaintExpecter();
337 public void sendSpecialKey(SpecialKey button
) {
340 sendKeyCode(KeyEvent
.KEYCODE_DPAD_DOWN
);
343 sendKeyCode(KeyEvent
.KEYCODE_DPAD_UP
);
346 sendKeyCode(KeyEvent
.KEYCODE_DPAD_LEFT
);
349 sendKeyCode(KeyEvent
.KEYCODE_DPAD_RIGHT
);
352 sendKeyCode(KeyEvent
.KEYCODE_ENTER
);
355 sendKeyCode(KeyEvent
.KEYCODE_MENU
);
358 sendKeyCode(KeyEvent
.KEYCODE_BACK
);
361 mAsserter
.ok(false, "sendSpecialKey", "Unknown SpecialKey " + button
);
366 public void sendKeyCode(int keyCode
) {
367 if (keyCode
<= 0 || keyCode
> KeyEvent
.getMaxKeyCode()) {
368 mAsserter
.ok(false, "sendKeyCode", "Unknown keyCode " + keyCode
);
370 mInstr
.sendCharacterSync(keyCode
);
374 public void sendKeys(String input
) {
375 mInstr
.sendStringSync(input
);
378 public void drag(int startingX
, int endingX
, int startingY
, int endingY
) {
379 mSolo
.drag(startingX
, endingX
, startingY
, endingY
, 10);
382 public Cursor
querySql(final String dbPath
, final String sql
) {
383 return new SQLiteBridge(dbPath
).rawQuery(sql
, null);