1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/build/mobile/robocop/FennecNativeActions.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,385 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko; 1.9 + 1.10 +import java.util.concurrent.BlockingQueue; 1.11 +import java.util.concurrent.LinkedBlockingQueue; 1.12 +import java.util.concurrent.TimeUnit; 1.13 + 1.14 +import org.json.JSONObject; 1.15 +import org.mozilla.gecko.FennecNativeDriver.LogLevel; 1.16 +import org.mozilla.gecko.gfx.LayerView; 1.17 +import org.mozilla.gecko.gfx.LayerView.DrawListener; 1.18 +import org.mozilla.gecko.mozglue.GeckoLoader; 1.19 +import org.mozilla.gecko.sqlite.SQLiteBridge; 1.20 +import org.mozilla.gecko.util.GeckoEventListener; 1.21 + 1.22 +import android.app.Activity; 1.23 +import android.app.Instrumentation; 1.24 +import android.database.Cursor; 1.25 +import android.os.SystemClock; 1.26 +import android.text.TextUtils; 1.27 +import android.view.KeyEvent; 1.28 + 1.29 +import com.jayway.android.robotium.solo.Solo; 1.30 + 1.31 +public class FennecNativeActions implements Actions { 1.32 + private static final String LOGTAG = "FennecNativeActions"; 1.33 + 1.34 + private Solo mSolo; 1.35 + private Instrumentation mInstr; 1.36 + private Assert mAsserter; 1.37 + 1.38 + public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation, Assert asserter) { 1.39 + mSolo = robocop; 1.40 + mInstr = instrumentation; 1.41 + mAsserter = asserter; 1.42 + 1.43 + GeckoLoader.loadSQLiteLibs(activity, activity.getApplication().getPackageResourcePath()); 1.44 + } 1.45 + 1.46 + class GeckoEventExpecter implements RepeatedEventExpecter { 1.47 + private static final int MAX_WAIT_MS = 90000; 1.48 + 1.49 + private volatile boolean mIsRegistered; 1.50 + 1.51 + private final String mGeckoEvent; 1.52 + private final GeckoEventListener mListener; 1.53 + 1.54 + private volatile boolean mEventEverReceived; 1.55 + private String mEventData; 1.56 + private BlockingQueue<String> mEventDataQueue; 1.57 + 1.58 + GeckoEventExpecter(final String geckoEvent) { 1.59 + if (TextUtils.isEmpty(geckoEvent)) { 1.60 + throw new IllegalArgumentException("geckoEvent must not be empty"); 1.61 + } 1.62 + 1.63 + mGeckoEvent = geckoEvent; 1.64 + mEventDataQueue = new LinkedBlockingQueue<String>(); 1.65 + 1.66 + final GeckoEventExpecter expecter = this; 1.67 + mListener = new GeckoEventListener() { 1.68 + @Override 1.69 + public void handleMessage(final String event, final JSONObject message) { 1.70 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, 1.71 + "handleMessage called for: " + event + "; expecting: " + mGeckoEvent); 1.72 + mAsserter.is(event, mGeckoEvent, "Given message occurred for registered event: " + message); 1.73 + 1.74 + expecter.notifyOfEvent(message); 1.75 + } 1.76 + }; 1.77 + 1.78 + GeckoAppShell.registerEventListener(mGeckoEvent, mListener); 1.79 + mIsRegistered = true; 1.80 + } 1.81 + 1.82 + public void blockForEvent() { 1.83 + blockForEvent(MAX_WAIT_MS, true); 1.84 + } 1.85 + 1.86 + public void blockForEvent(long millis, boolean failOnTimeout) { 1.87 + if (!mIsRegistered) { 1.88 + throw new IllegalStateException("listener not registered"); 1.89 + } 1.90 + 1.91 + try { 1.92 + mEventData = mEventDataQueue.poll(millis, TimeUnit.MILLISECONDS); 1.93 + } catch (InterruptedException ie) { 1.94 + FennecNativeDriver.log(LogLevel.ERROR, ie); 1.95 + } 1.96 + if (mEventData == null) { 1.97 + if (failOnTimeout) { 1.98 + FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); 1.99 + mAsserter.ok(false, "GeckoEventExpecter", 1.100 + "blockForEvent timeout: "+mGeckoEvent); 1.101 + } else { 1.102 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, 1.103 + "blockForEvent timeout: "+mGeckoEvent); 1.104 + } 1.105 + } else { 1.106 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, 1.107 + "unblocked on expecter for " + mGeckoEvent); 1.108 + } 1.109 + } 1.110 + 1.111 + public void blockUntilClear(long millis) { 1.112 + if (!mIsRegistered) { 1.113 + throw new IllegalStateException("listener not registered"); 1.114 + } 1.115 + if (millis <= 0) { 1.116 + throw new IllegalArgumentException("millis must be > 0"); 1.117 + } 1.118 + 1.119 + // wait for at least one event 1.120 + try { 1.121 + mEventData = mEventDataQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS); 1.122 + } catch (InterruptedException ie) { 1.123 + FennecNativeDriver.log(LogLevel.ERROR, ie); 1.124 + } 1.125 + if (mEventData == null) { 1.126 + FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); 1.127 + mAsserter.ok(false, "GeckoEventExpecter", "blockUntilClear timeout"); 1.128 + return; 1.129 + } 1.130 + // now wait for a period of millis where we don't get an event 1.131 + while (true) { 1.132 + try { 1.133 + mEventData = mEventDataQueue.poll(millis, TimeUnit.MILLISECONDS); 1.134 + } catch (InterruptedException ie) { 1.135 + FennecNativeDriver.log(LogLevel.INFO, ie); 1.136 + } 1.137 + if (mEventData == null) { 1.138 + // success 1.139 + break; 1.140 + } 1.141 + } 1.142 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, 1.143 + "unblocked on expecter for " + mGeckoEvent); 1.144 + } 1.145 + 1.146 + public String blockForEventData() { 1.147 + blockForEvent(); 1.148 + return mEventData; 1.149 + } 1.150 + 1.151 + public String blockForEventDataWithTimeout(long millis) { 1.152 + blockForEvent(millis, false); 1.153 + return mEventData; 1.154 + } 1.155 + 1.156 + public void unregisterListener() { 1.157 + if (!mIsRegistered) { 1.158 + throw new IllegalStateException("listener not registered"); 1.159 + } 1.160 + 1.161 + FennecNativeDriver.log(LogLevel.INFO, 1.162 + "EventExpecter: no longer listening for " + mGeckoEvent); 1.163 + 1.164 + GeckoAppShell.unregisterEventListener(mGeckoEvent, mListener); 1.165 + mIsRegistered = false; 1.166 + } 1.167 + 1.168 + public boolean eventReceived() { 1.169 + return mEventEverReceived; 1.170 + } 1.171 + 1.172 + void notifyOfEvent(final JSONObject message) { 1.173 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, 1.174 + "received event " + mGeckoEvent); 1.175 + 1.176 + mEventEverReceived = true; 1.177 + 1.178 + try { 1.179 + mEventDataQueue.put(message.toString()); 1.180 + } catch (InterruptedException e) { 1.181 + FennecNativeDriver.log(LogLevel.ERROR, 1.182 + "EventExpecter dropped event: " + message.toString(), e); 1.183 + } 1.184 + } 1.185 + } 1.186 + 1.187 + public RepeatedEventExpecter expectGeckoEvent(final String geckoEvent) { 1.188 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent); 1.189 + return new GeckoEventExpecter(geckoEvent); 1.190 + } 1.191 + 1.192 + public void sendGeckoEvent(final String geckoEvent, final String data) { 1.193 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(geckoEvent, data)); 1.194 + } 1.195 + 1.196 + public void sendPreferencesGetEvent(int requestId, String[] prefNames) { 1.197 + GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames)); 1.198 + } 1.199 + 1.200 + public void sendPreferencesObserveEvent(int requestId, String[] prefNames) { 1.201 + GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames)); 1.202 + } 1.203 + 1.204 + public void sendPreferencesRemoveObserversEvent(int requestId) { 1.205 + GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId)); 1.206 + } 1.207 + 1.208 + class PaintExpecter implements RepeatedEventExpecter { 1.209 + private static final int MAX_WAIT_MS = 90000; 1.210 + 1.211 + private boolean mPaintDone; 1.212 + private boolean mListening; 1.213 + 1.214 + private final LayerView mLayerView; 1.215 + private final DrawListener mDrawListener; 1.216 + 1.217 + PaintExpecter() { 1.218 + final PaintExpecter expecter = this; 1.219 + mLayerView = GeckoAppShell.getLayerView(); 1.220 + mDrawListener = new DrawListener() { 1.221 + @Override 1.222 + public void drawFinished() { 1.223 + FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, 1.224 + "Received drawFinished notification"); 1.225 + expecter.notifyOfEvent(); 1.226 + } 1.227 + }; 1.228 + mLayerView.addDrawListener(mDrawListener); 1.229 + mListening = true; 1.230 + } 1.231 + 1.232 + private synchronized void notifyOfEvent() { 1.233 + mPaintDone = true; 1.234 + this.notifyAll(); 1.235 + } 1.236 + 1.237 + public synchronized void blockForEvent(long millis, boolean failOnTimeout) { 1.238 + if (!mListening) { 1.239 + throw new IllegalStateException("draw listener not registered"); 1.240 + } 1.241 + long startTime = SystemClock.uptimeMillis(); 1.242 + long endTime = 0; 1.243 + while (!mPaintDone) { 1.244 + try { 1.245 + this.wait(millis); 1.246 + } catch (InterruptedException ie) { 1.247 + FennecNativeDriver.log(LogLevel.ERROR, ie); 1.248 + break; 1.249 + } 1.250 + endTime = SystemClock.uptimeMillis(); 1.251 + if (!mPaintDone && (endTime - startTime >= millis)) { 1.252 + if (failOnTimeout) { 1.253 + FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); 1.254 + mAsserter.ok(false, "PaintExpecter", "blockForEvent timeout"); 1.255 + } 1.256 + return; 1.257 + } 1.258 + } 1.259 + } 1.260 + 1.261 + public synchronized void blockForEvent() { 1.262 + blockForEvent(MAX_WAIT_MS, true); 1.263 + } 1.264 + 1.265 + public synchronized String blockForEventData() { 1.266 + blockForEvent(); 1.267 + return null; 1.268 + } 1.269 + 1.270 + public synchronized String blockForEventDataWithTimeout(long millis) { 1.271 + blockForEvent(millis, false); 1.272 + return null; 1.273 + } 1.274 + 1.275 + public synchronized boolean eventReceived() { 1.276 + return mPaintDone; 1.277 + } 1.278 + 1.279 + public synchronized void blockUntilClear(long millis) { 1.280 + if (!mListening) { 1.281 + throw new IllegalStateException("draw listener not registered"); 1.282 + } 1.283 + if (millis <= 0) { 1.284 + throw new IllegalArgumentException("millis must be > 0"); 1.285 + } 1.286 + // wait for at least one event 1.287 + long startTime = SystemClock.uptimeMillis(); 1.288 + long endTime = 0; 1.289 + while (!mPaintDone) { 1.290 + try { 1.291 + this.wait(MAX_WAIT_MS); 1.292 + } catch (InterruptedException ie) { 1.293 + FennecNativeDriver.log(LogLevel.ERROR, ie); 1.294 + break; 1.295 + } 1.296 + endTime = SystemClock.uptimeMillis(); 1.297 + if (!mPaintDone && (endTime - startTime >= MAX_WAIT_MS)) { 1.298 + FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR); 1.299 + mAsserter.ok(false, "PaintExpecter", "blockUtilClear timeout"); 1.300 + return; 1.301 + } 1.302 + } 1.303 + // now wait for a period of millis where we don't get an event 1.304 + startTime = SystemClock.uptimeMillis(); 1.305 + while (true) { 1.306 + try { 1.307 + this.wait(millis); 1.308 + } catch (InterruptedException ie) { 1.309 + FennecNativeDriver.log(LogLevel.ERROR, ie); 1.310 + break; 1.311 + } 1.312 + endTime = SystemClock.uptimeMillis(); 1.313 + if (endTime - startTime >= millis) { 1.314 + // success 1.315 + break; 1.316 + } 1.317 + 1.318 + // we got a notify() before we could wait long enough, so we need to start over 1.319 + // Note, moving the goal post might have us race against a "drawFinished" flood 1.320 + startTime = endTime; 1.321 + } 1.322 + } 1.323 + 1.324 + public synchronized void unregisterListener() { 1.325 + if (!mListening) { 1.326 + throw new IllegalStateException("listener not registered"); 1.327 + } 1.328 + 1.329 + FennecNativeDriver.log(LogLevel.INFO, 1.330 + "PaintExpecter: no longer listening for events"); 1.331 + mLayerView.removeDrawListener(mDrawListener); 1.332 + mListening = false; 1.333 + } 1.334 + } 1.335 + 1.336 + public RepeatedEventExpecter expectPaint() { 1.337 + return new PaintExpecter(); 1.338 + } 1.339 + 1.340 + public void sendSpecialKey(SpecialKey button) { 1.341 + switch(button) { 1.342 + case DOWN: 1.343 + sendKeyCode(KeyEvent.KEYCODE_DPAD_DOWN); 1.344 + break; 1.345 + case UP: 1.346 + sendKeyCode(KeyEvent.KEYCODE_DPAD_UP); 1.347 + break; 1.348 + case LEFT: 1.349 + sendKeyCode(KeyEvent.KEYCODE_DPAD_LEFT); 1.350 + break; 1.351 + case RIGHT: 1.352 + sendKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT); 1.353 + break; 1.354 + case ENTER: 1.355 + sendKeyCode(KeyEvent.KEYCODE_ENTER); 1.356 + break; 1.357 + case MENU: 1.358 + sendKeyCode(KeyEvent.KEYCODE_MENU); 1.359 + break; 1.360 + case BACK: 1.361 + sendKeyCode(KeyEvent.KEYCODE_BACK); 1.362 + break; 1.363 + default: 1.364 + mAsserter.ok(false, "sendSpecialKey", "Unknown SpecialKey " + button); 1.365 + break; 1.366 + } 1.367 + } 1.368 + 1.369 + public void sendKeyCode(int keyCode) { 1.370 + if (keyCode <= 0 || keyCode > KeyEvent.getMaxKeyCode()) { 1.371 + mAsserter.ok(false, "sendKeyCode", "Unknown keyCode " + keyCode); 1.372 + } 1.373 + mInstr.sendCharacterSync(keyCode); 1.374 + } 1.375 + 1.376 + @Override 1.377 + public void sendKeys(String input) { 1.378 + mInstr.sendStringSync(input); 1.379 + } 1.380 + 1.381 + public void drag(int startingX, int endingX, int startingY, int endingY) { 1.382 + mSolo.drag(startingX, endingX, startingY, endingY, 10); 1.383 + } 1.384 + 1.385 + public Cursor querySql(final String dbPath, final String sql) { 1.386 + return new SQLiteBridge(dbPath).rawQuery(sql, null); 1.387 + } 1.388 +}