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