1/*
2 * Copyright (C) 2010 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.android.dumprendertree2;
18
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.net.http.SslError;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Message;
29import android.os.Messenger;
30import android.os.PowerManager;
31import android.os.PowerManager.WakeLock;
32import android.os.Process;
33import android.os.RemoteException;
34import android.util.Log;
35import android.view.Window;
36import android.webkit.ConsoleMessage;
37import android.webkit.GeolocationPermissions;
38import android.webkit.HttpAuthHandler;
39import android.webkit.JsPromptResult;
40import android.webkit.JsResult;
41import android.webkit.SslErrorHandler;
42import android.webkit.WebChromeClient;
43import android.webkit.WebSettings;
44import android.webkit.WebSettingsClassic;
45import android.webkit.WebStorage;
46import android.webkit.WebStorage.QuotaUpdater;
47import android.webkit.WebView;
48import android.webkit.WebViewClassic;
49import android.webkit.WebViewClient;
50
51import java.lang.Thread.UncaughtExceptionHandler;
52import java.util.HashMap;
53import java.util.Iterator;
54import java.util.List;
55import java.util.Map;
56
57/**
58 * This activity executes the test. It contains WebView and logic of LayoutTestController
59 * functions. It runs in a separate process and sends the results of running the test
60 * to ManagerService. The reason why is to handle crashing (test that crashes brings down
61 * whole process with it).
62 */
63public class LayoutTestsExecutor extends Activity {
64
65    private enum CurrentState {
66        IDLE,
67        RENDERING_PAGE,
68        WAITING_FOR_ASYNCHRONOUS_TEST,
69        OBTAINING_RESULT;
70
71        public boolean isRunningState() {
72            return this == CurrentState.RENDERING_PAGE ||
73                    this == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
74        }
75    }
76
77    private static final String LOG_TAG = "LayoutTestsExecutor";
78
79    public static final String EXTRA_TESTS_FILE = "TestsList";
80    public static final String EXTRA_TEST_INDEX = "TestIndex";
81
82    private static final int MSG_ACTUAL_RESULT_OBTAINED = 0;
83    private static final int MSG_TEST_TIMED_OUT = 1;
84
85    private static final int DEFAULT_TIME_OUT_MS = 15 * 1000;
86
87    /** A list of tests that remain to run since last crash */
88    private List<String> mTestsList;
89
90    /**
91     * This is a number of currently running test. It is 0-based and doesn't reset after
92     * the crash. Initial index is passed to LayoutTestsExecuter in the intent that starts
93     * it.
94     */
95    private int mCurrentTestIndex;
96
97    /** The total number of tests to run, doesn't reset after crash */
98    private int mTotalTestCount;
99
100    private WebView mCurrentWebView;
101    private String mCurrentTestRelativePath;
102    private String mCurrentTestUri;
103    private CurrentState mCurrentState = CurrentState.IDLE;
104
105    private boolean mCurrentTestTimedOut;
106    private AbstractResult mCurrentResult;
107    private AdditionalTextOutput mCurrentAdditionalTextOutput;
108
109    private LayoutTestController mLayoutTestController = new LayoutTestController(this);
110    private boolean mCanOpenWindows;
111    private boolean mDumpDatabaseCallbacks;
112
113    private EventSender mEventSender = new EventSender();
114
115    private WakeLock mScreenDimLock;
116
117    /** COMMUNICATION WITH ManagerService */
118
119    private Messenger mManagerServiceMessenger;
120
121    private ServiceConnection mServiceConnection = new ServiceConnection() {
122
123        @Override
124        public void onServiceConnected(ComponentName name, IBinder service) {
125            mManagerServiceMessenger = new Messenger(service);
126            startTests();
127        }
128
129        @Override
130        public void onServiceDisconnected(ComponentName name) {
131            /** TODO */
132        }
133    };
134
135    private final Handler mResultHandler = new Handler() {
136        @Override
137        public void handleMessage(Message msg) {
138            switch (msg.what) {
139                case MSG_ACTUAL_RESULT_OBTAINED:
140                    onActualResultsObtained();
141                    break;
142
143                case MSG_TEST_TIMED_OUT:
144                    onTestTimedOut();
145                    break;
146
147                default:
148                    break;
149            }
150        }
151    };
152
153    /** WEBVIEW CONFIGURATION */
154
155    private WebViewClient mWebViewClient = new WebViewClient() {
156        @Override
157        public void onPageFinished(WebView view, String url) {
158            /** Some tests fire up many page loads, we don't want to detect them */
159            if (!url.equals(mCurrentTestUri)) {
160                return;
161            }
162
163            if (mCurrentState == CurrentState.RENDERING_PAGE) {
164                onTestFinished();
165            }
166        }
167
168         @Override
169         public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
170                 String host, String realm) {
171             if (handler.useHttpAuthUsernamePassword() && view != null) {
172                 String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
173                 if (credentials != null && credentials.length == 2) {
174                     handler.proceed(credentials[0], credentials[1]);
175                     return;
176                 }
177             }
178             handler.cancel();
179         }
180
181         @Override
182         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
183             // We ignore SSL errors. In particular, the certificate used by the LayoutTests server
184             // produces an error as it lacks a CN field.
185             handler.proceed();
186         }
187    };
188
189    private WebChromeClient mWebChromeClient = new WebChromeClient() {
190        @Override
191        public void onExceededDatabaseQuota(String url, String databaseIdentifier,
192                long currentQuota, long estimatedSize, long totalUsedQuota,
193                QuotaUpdater quotaUpdater) {
194            /** TODO: This should be recorded as part of the text result */
195            /** TODO: The quota should also probably be reset somehow for every test? */
196            if (mDumpDatabaseCallbacks) {
197                getCurrentAdditionalTextOutput().appendExceededDbQuotaMessage(url,
198                        databaseIdentifier);
199            }
200            quotaUpdater.updateQuota(currentQuota + 5 * 1024 * 1024);
201        }
202
203        @Override
204        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
205            getCurrentAdditionalTextOutput().appendJsAlert(message);
206            result.confirm();
207            return true;
208        }
209
210        @Override
211        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
212            getCurrentAdditionalTextOutput().appendJsConfirm(message);
213            result.confirm();
214            return true;
215        }
216
217        @Override
218        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
219                JsPromptResult result) {
220            getCurrentAdditionalTextOutput().appendJsPrompt(message, defaultValue);
221            result.confirm();
222            return true;
223        }
224
225        @Override
226        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
227            getCurrentAdditionalTextOutput().appendConsoleMessage(consoleMessage);
228            return true;
229        }
230
231        @Override
232        public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture,
233                Message resultMsg) {
234            WebView.WebViewTransport transport = (WebView.WebViewTransport)resultMsg.obj;
235            /** By default windows cannot be opened, so just send null back. */
236            WebView newWindowWebView = null;
237
238            if (mCanOpenWindows) {
239                /**
240                 * We never display the new window, just create the view and allow it's content to
241                 * execute and be recorded by the executor.
242                 */
243                newWindowWebView = createWebViewWithJavascriptInterfaces();
244                setupWebView(newWindowWebView);
245            }
246
247            transport.setWebView(newWindowWebView);
248            resultMsg.sendToTarget();
249            return true;
250        }
251
252        @Override
253        public void onGeolocationPermissionsShowPrompt(String origin,
254                GeolocationPermissions.Callback callback) {
255            throw new RuntimeException(
256                    "The WebCore mock used by DRT should bypass the usual permissions flow.");
257        }
258    };
259
260    /** IMPLEMENTATION */
261
262    @Override
263    protected void onCreate(Bundle savedInstanceState) {
264        super.onCreate(savedInstanceState);
265
266        /**
267         * It detects the crash by catching all the uncaught exceptions. However, we
268         * still have to kill the process, because after catching the exception the
269         * activity remains in a strange state, where intents don't revive it.
270         * However, we send the message to the service to speed up the rebooting
271         * (we don't have to wait for time-out to kick in).
272         */
273        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
274            @Override
275            public void uncaughtException(Thread thread, Throwable e) {
276                Log.w(LOG_TAG,
277                        "onTestCrashed(): " + mCurrentTestRelativePath + " thread=" + thread, e);
278
279                try {
280                    Message serviceMsg =
281                            Message.obtain(null, ManagerService.MSG_CURRENT_TEST_CRASHED);
282
283                    mManagerServiceMessenger.send(serviceMsg);
284                } catch (RemoteException e2) {
285                    Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e2);
286                }
287
288                Process.killProcess(Process.myPid());
289            }
290        });
291
292        requestWindowFeature(Window.FEATURE_PROGRESS);
293
294        Intent intent = getIntent();
295        mTestsList = FsUtils.loadTestListFromStorage(intent.getStringExtra(EXTRA_TESTS_FILE));
296        mCurrentTestIndex = intent.getIntExtra(EXTRA_TEST_INDEX, -1);
297        mTotalTestCount = mCurrentTestIndex + mTestsList.size();
298
299        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
300        mScreenDimLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
301                | PowerManager.ON_AFTER_RELEASE, "WakeLock in LayoutTester");
302        mScreenDimLock.acquire();
303
304        bindService(new Intent(this, ManagerService.class), mServiceConnection,
305                Context.BIND_AUTO_CREATE);
306    }
307
308    private void reset() {
309        WebView previousWebView = mCurrentWebView;
310
311        resetLayoutTestController();
312
313        mCurrentTestTimedOut = false;
314        mCurrentResult = null;
315        mCurrentAdditionalTextOutput = null;
316
317        mCurrentWebView = createWebViewWithJavascriptInterfaces();
318        // When we create the first WebView, we need to pause to wait for the WebView thread to spin
319        // and up and for it to register its message handlers.
320        if (previousWebView == null) {
321            try {
322                Thread.currentThread().sleep(1000);
323            } catch (Exception e) {}
324        }
325        setupWebView(mCurrentWebView);
326
327        mEventSender.reset(mCurrentWebView);
328
329        setContentView(mCurrentWebView);
330        if (previousWebView != null) {
331            Log.d(LOG_TAG + "::reset", "previousWebView != null");
332            previousWebView.destroy();
333        }
334    }
335
336    private static class WebViewWithJavascriptInterfaces extends WebView {
337        public WebViewWithJavascriptInterfaces(
338                Context context, Map<String, Object> javascriptInterfaces) {
339            super(context,
340                  null, // attribute set
341                  0, // default style resource ID
342                  javascriptInterfaces,
343                  false); // is private browsing
344        }
345    }
346    private WebView createWebViewWithJavascriptInterfaces() {
347        Map<String, Object> javascriptInterfaces = new HashMap<String, Object>();
348        javascriptInterfaces.put("layoutTestController", mLayoutTestController);
349        javascriptInterfaces.put("eventSender", mEventSender);
350        return new WebViewWithJavascriptInterfaces(this, javascriptInterfaces);
351    }
352
353    private void setupWebView(WebView webView) {
354        webView.setWebViewClient(mWebViewClient);
355        webView.setWebChromeClient(mWebChromeClient);
356
357        /**
358         * Setting a touch interval of -1 effectively disables the optimisation in WebView
359         * that stops repeated touch events flooding WebCore. The Event Sender only sends a
360         * single event rather than a stream of events (like what would generally happen in
361         * a real use of touch events in a WebView)  and so if the WebView drops the event,
362         * the test will fail as the test expects one callback for every touch it synthesizes.
363         */
364        WebViewClassic webViewClassic = WebViewClassic.fromWebView(webView);
365        webViewClassic.setTouchInterval(-1);
366
367        webViewClassic.clearCache(true);
368
369        WebSettingsClassic webViewSettings = webViewClassic.getSettings();
370        webViewSettings.setAppCacheEnabled(true);
371        webViewSettings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
372        // Use of larger values causes unexplained AppCache database corruption.
373        // TODO: Investigate what's really going on here.
374        webViewSettings.setAppCacheMaxSize(100 * 1024 * 1024);
375        webViewSettings.setJavaScriptEnabled(true);
376        webViewSettings.setJavaScriptCanOpenWindowsAutomatically(true);
377        webViewSettings.setSupportMultipleWindows(true);
378        webViewSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
379        webViewSettings.setDatabaseEnabled(true);
380        webViewSettings.setDatabasePath(getDir("databases", 0).getAbsolutePath());
381        webViewSettings.setDomStorageEnabled(true);
382        webViewSettings.setWorkersEnabled(false);
383        webViewSettings.setXSSAuditorEnabled(false);
384        webViewSettings.setPageCacheCapacity(0);
385
386        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
387        WebViewClassic.fromWebView(mCurrentWebView).setUseMockGeolocation();
388        WebViewClassic.fromWebView(mCurrentWebView).setUseMockDeviceOrientation();
389
390        // Must do this after setting the AppCache path.
391        WebStorage.getInstance().deleteAllData();
392    }
393
394    private void startTests() {
395        // This is called when the tests are started and after each crash.
396        // We only send the reset message in the former case.
397        if (mCurrentTestIndex <= 0) {
398            sendResetMessage();
399        }
400        if (mCurrentTestIndex == 0) {
401            sendFirstTestMessage();
402        }
403
404        runNextTest();
405    }
406
407    private void sendResetMessage() {
408        try {
409            Message serviceMsg = Message.obtain(null, ManagerService.MSG_RESET);
410            mManagerServiceMessenger.send(serviceMsg);
411        } catch (RemoteException e) {
412            Log.e(LOG_TAG, "Error sending message to manager service:", e);
413        }
414    }
415
416    private void sendFirstTestMessage() {
417        try {
418            Message serviceMsg = Message.obtain(null, ManagerService.MSG_FIRST_TEST);
419
420            Bundle bundle = new Bundle();
421            bundle.putString("firstTest", mTestsList.get(0));
422            bundle.putInt("index", mCurrentTestIndex);
423
424            serviceMsg.setData(bundle);
425            mManagerServiceMessenger.send(serviceMsg);
426        } catch (RemoteException e) {
427            Log.e(LOG_TAG, "Error sending message to manager service:", e);
428        }
429    }
430
431    private void runNextTest() {
432        assert mCurrentState == CurrentState.IDLE : "mCurrentState = " + mCurrentState.name();
433
434        if (mTestsList.isEmpty()) {
435            onAllTestsFinished();
436            return;
437        }
438
439        mCurrentTestRelativePath = mTestsList.remove(0);
440
441        Log.i(LOG_TAG, "runNextTest(): Start: " + mCurrentTestRelativePath +
442                " (" + mCurrentTestIndex + ")");
443
444        mCurrentTestUri = FileFilter.getUrl(mCurrentTestRelativePath, true).toString();
445
446        reset();
447
448        /** Start time-out countdown and the test */
449        mCurrentState = CurrentState.RENDERING_PAGE;
450        mResultHandler.sendEmptyMessageDelayed(MSG_TEST_TIMED_OUT, DEFAULT_TIME_OUT_MS);
451        mCurrentWebView.loadUrl(mCurrentTestUri);
452    }
453
454    private void onTestTimedOut() {
455        assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
456
457        Log.w(LOG_TAG, "onTestTimedOut(): " + mCurrentTestRelativePath);
458        mCurrentTestTimedOut = true;
459
460        /**
461         * While it is theoretically possible that the test times out because
462         * of webview becoming unresponsive, it is very unlikely. Therefore it's
463         * assumed that obtaining results (that calls various webview methods)
464         * will not itself hang.
465         */
466        obtainActualResultsFromWebView();
467    }
468
469    private void onTestFinished() {
470        assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
471
472        Log.i(LOG_TAG, "onTestFinished(): " + mCurrentTestRelativePath);
473        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
474        obtainActualResultsFromWebView();
475    }
476
477    private void obtainActualResultsFromWebView() {
478        /**
479         * If the result has not been set by the time the test finishes we create
480         * a default type of result.
481         */
482        if (mCurrentResult == null) {
483            /** TODO: Default type should be RenderTreeResult. We don't support it now. */
484            mCurrentResult = new TextResult(mCurrentTestRelativePath);
485        }
486
487        mCurrentState = CurrentState.OBTAINING_RESULT;
488
489        if (mCurrentTestTimedOut) {
490            mCurrentResult.setDidTimeOut();
491        }
492        mCurrentResult.obtainActualResults(mCurrentWebView,
493                mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
494    }
495
496    private void onActualResultsObtained() {
497        assert mCurrentState == CurrentState.OBTAINING_RESULT
498                : "mCurrentState = " + mCurrentState.name();
499
500        Log.i(LOG_TAG, "onActualResultsObtained(): " + mCurrentTestRelativePath);
501        mCurrentState = CurrentState.IDLE;
502
503        reportResultToService();
504        mCurrentTestIndex++;
505        updateProgressBar();
506        runNextTest();
507    }
508
509    private void reportResultToService() {
510        if (mCurrentAdditionalTextOutput != null) {
511            mCurrentResult.setAdditionalTextOutputString(mCurrentAdditionalTextOutput.toString());
512        }
513
514        try {
515            Message serviceMsg =
516                    Message.obtain(null, ManagerService.MSG_PROCESS_ACTUAL_RESULTS);
517
518            Bundle bundle = mCurrentResult.getBundle();
519            bundle.putInt("testIndex", mCurrentTestIndex);
520            if (!mTestsList.isEmpty()) {
521                bundle.putString("nextTest", mTestsList.get(0));
522            }
523
524            serviceMsg.setData(bundle);
525            mManagerServiceMessenger.send(serviceMsg);
526        } catch (RemoteException e) {
527            Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
528        }
529    }
530
531    private void updateProgressBar() {
532        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
533                mCurrentTestIndex * Window.PROGRESS_END / mTotalTestCount);
534        setTitle(mCurrentTestIndex * 100 / mTotalTestCount + "% " +
535                "(" + mCurrentTestIndex + "/" + mTotalTestCount + ")");
536    }
537
538    private void onAllTestsFinished() {
539        mScreenDimLock.release();
540
541        try {
542            Message serviceMsg =
543                    Message.obtain(null, ManagerService.MSG_ALL_TESTS_FINISHED);
544            mManagerServiceMessenger.send(serviceMsg);
545        } catch (RemoteException e) {
546            Log.e(LOG_TAG, "mCurrentTestRelativePath=" + mCurrentTestRelativePath, e);
547        }
548
549        unbindService(mServiceConnection);
550    }
551
552    private AdditionalTextOutput getCurrentAdditionalTextOutput() {
553        if (mCurrentAdditionalTextOutput == null) {
554            mCurrentAdditionalTextOutput = new AdditionalTextOutput();
555        }
556        return mCurrentAdditionalTextOutput;
557    }
558
559    /** LAYOUT TEST CONTROLLER */
560
561    private static final int MSG_WAIT_UNTIL_DONE = 0;
562    private static final int MSG_NOTIFY_DONE = 1;
563    private static final int MSG_DUMP_AS_TEXT = 2;
564    private static final int MSG_DUMP_CHILD_FRAMES_AS_TEXT = 3;
565    private static final int MSG_SET_CAN_OPEN_WINDOWS = 4;
566    private static final int MSG_DUMP_DATABASE_CALLBACKS = 5;
567    private static final int MSG_OVERRIDE_PREFERENCE = 6;
568    private static final int MSG_SET_XSS_AUDITOR_ENABLED = 7;
569
570    /** String constants for use with layoutTestController.overridePreference() */
571    private final String WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED =
572            "WebKitOfflineWebApplicationCacheEnabled";
573    private final String WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY = "WebKitUsesPageCachePreferenceKey";
574
575    Handler mLayoutTestControllerHandler = new Handler() {
576        @Override
577        public void handleMessage(Message msg) {
578            assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
579
580            switch (msg.what) {
581                case MSG_DUMP_AS_TEXT:
582                    if (mCurrentResult == null) {
583                        mCurrentResult = new TextResult(mCurrentTestRelativePath);
584                    }
585                    assert mCurrentResult instanceof TextResult
586                            : "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
587                    break;
588
589                case MSG_DUMP_CHILD_FRAMES_AS_TEXT:
590                    /** If dumpAsText was not called we assume that the result should be text */
591                    if (mCurrentResult == null) {
592                        mCurrentResult = new TextResult(mCurrentTestRelativePath);
593                    }
594
595                    assert mCurrentResult instanceof TextResult
596                            : "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
597
598                    ((TextResult)mCurrentResult).setDumpChildFramesAsText(true);
599                    break;
600
601                case MSG_DUMP_DATABASE_CALLBACKS:
602                    mDumpDatabaseCallbacks = true;
603                    break;
604
605                case MSG_NOTIFY_DONE:
606                    if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) {
607                        onTestFinished();
608                    }
609                    break;
610
611                case MSG_OVERRIDE_PREFERENCE:
612                    /**
613                     * TODO: We should look up the correct WebView for the frame which
614                     * called the layoutTestController method. Currently, we just use the
615                     * WebView for the main frame. EventSender suffers from the same
616                     * problem.
617                     */
618                    String key = msg.getData().getString("key");
619                    boolean value = msg.getData().getBoolean("value");
620                    if (WEBKIT_OFFLINE_WEB_APPLICATION_CACHE_ENABLED.equals(key)) {
621                        WebViewClassic.fromWebView(mCurrentWebView).getSettings().
622                                setAppCacheEnabled(value);
623                    } else if (WEBKIT_USES_PAGE_CACHE_PREFERENCE_KEY.equals(key)) {
624                        // Cache the maximum possible number of pages.
625                        WebViewClassic.fromWebView(mCurrentWebView).getSettings().
626                                setPageCacheCapacity(Integer.MAX_VALUE);
627                    } else {
628                        Log.w(LOG_TAG, "LayoutTestController.overridePreference(): " +
629                              "Unsupported preference '" + key + "'");
630                    }
631                    break;
632
633                case MSG_SET_CAN_OPEN_WINDOWS:
634                    mCanOpenWindows = true;
635                    break;
636
637                case MSG_SET_XSS_AUDITOR_ENABLED:
638                    WebViewClassic.fromWebView(mCurrentWebView).getSettings().
639                            setXSSAuditorEnabled(msg.arg1 == 1);
640                    break;
641
642                case MSG_WAIT_UNTIL_DONE:
643                    mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
644                    break;
645
646                default:
647                    assert false : "msg.what=" + msg.what;
648                    break;
649            }
650        }
651    };
652
653    private void resetLayoutTestController() {
654        mCanOpenWindows = false;
655        mDumpDatabaseCallbacks = false;
656    }
657
658    public void dumpAsText(boolean enablePixelTest) {
659        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpAsText(" + enablePixelTest + ") called");
660        /** TODO: Implement */
661        if (enablePixelTest) {
662            Log.w(LOG_TAG, "enablePixelTest not implemented, switching to false");
663        }
664        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_AS_TEXT);
665    }
666
667    public void dumpChildFramesAsText() {
668        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpChildFramesAsText() called");
669        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_CHILD_FRAMES_AS_TEXT);
670    }
671
672    public void dumpDatabaseCallbacks() {
673        Log.i(LOG_TAG, mCurrentTestRelativePath + ": dumpDatabaseCallbacks() called");
674        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_DATABASE_CALLBACKS);
675    }
676
677    public void notifyDone() {
678        Log.i(LOG_TAG, mCurrentTestRelativePath + ": notifyDone() called");
679        mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE);
680    }
681
682    public void overridePreference(String key, boolean value) {
683        Log.i(LOG_TAG, mCurrentTestRelativePath + ": overridePreference(" + key + ", " + value +
684        ") called");
685        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_OVERRIDE_PREFERENCE);
686        msg.getData().putString("key", key);
687        msg.getData().putBoolean("value", value);
688        msg.sendToTarget();
689    }
690
691    public void setCanOpenWindows() {
692        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setCanOpenWindows() called");
693        mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS);
694    }
695
696    public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
697        WebViewClassic.fromWebView(mCurrentWebView).setMockGeolocationPosition(latitude, longitude,
698                accuracy);
699    }
700
701    public void setMockGeolocationError(int code, String message) {
702        WebViewClassic.fromWebView(mCurrentWebView).setMockGeolocationError(code, message);
703    }
704
705    public void setGeolocationPermission(boolean allow) {
706        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setGeolocationPermission(" + allow +
707                ") called");
708        WebViewClassic.fromWebView(mCurrentWebView).setMockGeolocationPermission(allow);
709    }
710
711    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
712            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
713        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setMockDeviceOrientation(" + canProvideAlpha +
714                ", " + alpha + ", " + canProvideBeta + ", " + beta + ", " + canProvideGamma +
715                ", " + gamma + ")");
716        WebViewClassic.fromWebView(mCurrentWebView).setMockDeviceOrientation(canProvideAlpha,
717                alpha, canProvideBeta, beta, canProvideGamma, gamma);
718    }
719
720    public void setXSSAuditorEnabled(boolean flag) {
721        Log.i(LOG_TAG, mCurrentTestRelativePath + ": setXSSAuditorEnabled(" + flag + ") called");
722        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_SET_XSS_AUDITOR_ENABLED);
723        msg.arg1 = flag ? 1 : 0;
724        msg.sendToTarget();
725    }
726
727    public void waitUntilDone() {
728        Log.i(LOG_TAG, mCurrentTestRelativePath + ": waitUntilDone() called");
729        mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE);
730    }
731
732}
733