1/*
2 * Copyright (C) 2009 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.dumprendertree;
18
19import android.app.Activity;
20import android.app.ActivityThread;
21import android.graphics.Bitmap;
22import android.net.http.SslError;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.util.Log;
27import android.view.ViewGroup;
28import android.webkit.HttpAuthHandler;
29import android.webkit.JsPromptResult;
30import android.webkit.JsResult;
31import android.webkit.SslErrorHandler;
32import android.webkit.WebChromeClient;
33import android.webkit.WebView;
34import android.webkit.WebViewClient;
35import android.webkit.WebSettings.LayoutAlgorithm;
36import android.widget.LinearLayout;
37import android.widget.LinearLayout.LayoutParams;
38
39public class ReliabilityTestActivity extends Activity {
40
41    public static final String TEST_URL_ACTION = "com.andrdoid.dumprendertree.TestUrlAction";
42    public static final String PARAM_URL = "URL";
43    public static final String PARAM_TIMEOUT = "Timeout";
44    public static final int RESULT_TIMEOUT = 0xDEAD;
45    public static final int MSG_TIMEOUT = 0xC001;
46    public static final int MSG_NAVIGATE = 0xC002;
47    public static final String MSG_NAV_URL = "url";
48    public static final String MSG_NAV_LOGTIME = "logtime";
49
50    private static final String LOGTAG = "ReliabilityTestActivity";
51
52    private WebView webView;
53    private SimpleWebViewClient webViewClient;
54    private SimpleChromeClient chromeClient;
55    private Handler handler;
56    private boolean timeoutFlag;
57    private boolean logTime;
58    private boolean pageDone;
59    private Object pageDoneLock;
60    private int pageStartCount;
61    private int manualDelay;
62    private long startTime;
63    private long pageLoadTime;
64    private PageDoneRunner pageDoneRunner = new PageDoneRunner();
65
66    @Override
67    protected void onCreate(Bundle savedInstanceState) {
68        super.onCreate(savedInstanceState);
69
70        Log.v(LOGTAG, "onCreate, inst=" + Integer.toHexString(hashCode()));
71
72        LinearLayout contentView = new LinearLayout(this);
73        contentView.setOrientation(LinearLayout.VERTICAL);
74        setContentView(contentView);
75        setTitle("Idle");
76
77        webView = new WebView(this);
78        webView.getSettings().setJavaScriptEnabled(true);
79        webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(false);
80        webView.getSettings().setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
81
82        webViewClient = new SimpleWebViewClient();
83        chromeClient = new SimpleChromeClient();
84        webView.setWebViewClient(webViewClient);
85        webView.setWebChromeClient(chromeClient);
86
87        contentView.addView(webView, new LayoutParams(
88                ViewGroup.LayoutParams.MATCH_PARENT,
89                ViewGroup.LayoutParams.MATCH_PARENT, 0.0f));
90
91        handler = new Handler() {
92            @Override
93            public void handleMessage(Message msg) {
94                switch (msg.what) {
95                    case MSG_TIMEOUT:
96                        handleTimeout();
97                        return;
98                    case MSG_NAVIGATE:
99                        manualDelay = msg.arg2;
100                        navigate(msg.getData().getString(MSG_NAV_URL), msg.arg1);
101                        logTime = msg.getData().getBoolean(MSG_NAV_LOGTIME);
102                        return;
103                }
104            }
105        };
106
107        pageDoneLock = new Object();
108    }
109
110    public void reset() {
111        synchronized (pageDoneLock) {
112            pageDone = false;
113        }
114        timeoutFlag = false;
115        pageStartCount = 0;
116        chromeClient.resetJsTimeout();
117    }
118
119    private void navigate(String url, int timeout) {
120        if(url == null) {
121            Log.v(LOGTAG, "URL is null, cancelling...");
122            finish();
123        }
124        webView.stopLoading();
125        if(logTime) {
126            webView.clearCache(true);
127        }
128        startTime = System.currentTimeMillis();
129        Log.v(LOGTAG, "Navigating to URL: " + url);
130        webView.loadUrl(url);
131
132        if(timeout != 0) {
133            //set a timer with specified timeout (in ms)
134            handler.sendMessageDelayed(handler.obtainMessage(MSG_TIMEOUT),
135                    timeout);
136        }
137    }
138
139    @Override
140    protected void onDestroy() {
141        super.onDestroy();
142        Log.v(LOGTAG, "onDestroy, inst=" + Integer.toHexString(hashCode()));
143        webView.clearCache(true);
144        webView.destroy();
145    }
146
147    private boolean isPageDone() {
148        synchronized (pageDoneLock) {
149            return pageDone;
150        }
151    }
152
153    private void setPageDone(boolean pageDone) {
154        synchronized (pageDoneLock) {
155            this.pageDone = pageDone;
156            pageDoneLock.notifyAll();
157        }
158    }
159
160    private void handleTimeout() {
161        int progress = webView.getProgress();
162        webView.stopLoading();
163        Log.v(LOGTAG, "Page timeout triggered, progress = " + progress);
164        timeoutFlag = true;
165        handler.postDelayed(pageDoneRunner, manualDelay);
166    }
167
168    public boolean waitUntilDone() {
169        validateNotAppThread();
170        synchronized (pageDoneLock) {
171            while(!isPageDone()) {
172                try {
173                    pageDoneLock.wait();
174                } catch (InterruptedException ie) {
175                    //no-op
176                }
177            }
178        }
179        return timeoutFlag;
180    }
181
182    public Handler getHandler() {
183        return handler;
184    }
185
186    private final void validateNotAppThread() {
187        if (ActivityThread.currentActivityThread() != null) {
188            throw new RuntimeException(
189                "This method can not be called from the main application thread");
190        }
191    }
192
193    public long getPageLoadTime() {
194        return pageLoadTime;
195    }
196
197    class SimpleWebViewClient extends WebViewClient {
198
199        @Override
200        public void onReceivedError(WebView view, int errorCode, String description,
201                String failingUrl) {
202            Log.v(LOGTAG, "Received WebCore error: code=" + errorCode
203                    + ", description=" + description
204                    + ", url=" + failingUrl);
205        }
206
207        @Override
208        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
209            //ignore certificate error
210            Log.v(LOGTAG, "Received SSL error: " + error.toString());
211            handler.proceed();
212        }
213
214        @Override
215        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
216                String realm) {
217            // cancel http auth request
218            handler.cancel();
219        }
220
221        @Override
222        public void onPageStarted(WebView view, String url, Bitmap favicon) {
223            pageStartCount++;
224            Log.v(LOGTAG, "onPageStarted: " + url);
225        }
226
227        @Override
228        public void onPageFinished(WebView view, String url) {
229            Log.v(LOGTAG, "onPageFinished: " + url);
230            // let handleTimeout take care of finishing the page
231            if(!timeoutFlag)
232                handler.postDelayed(new WebViewStatusChecker(), 500);
233        }
234    }
235
236    class SimpleChromeClient extends WebChromeClient {
237
238        private int timeoutCounter = 0;
239
240        @Override
241        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
242            result.confirm();
243            return true;
244        }
245
246        @Override
247        public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
248            result.confirm();
249            return true;
250        }
251
252        @Override
253        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
254            result.confirm();
255            return true;
256        }
257
258        @Override
259        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
260                JsPromptResult result) {
261            result.confirm();
262            return true;
263        }
264
265        @Override
266        public boolean onJsTimeout() {
267            timeoutCounter++;
268            Log.v(LOGTAG, "JavaScript timeout, count=" + timeoutCounter);
269            return timeoutCounter > 2;
270        }
271
272        public void resetJsTimeout() {
273            timeoutCounter = 0;
274        }
275
276        @Override
277        public void onReceivedTitle(WebView view, String title) {
278            ReliabilityTestActivity.this.setTitle(title);
279        }
280    }
281
282    class WebViewStatusChecker implements Runnable {
283
284        private int initialStartCount;
285
286        public WebViewStatusChecker() {
287            initialStartCount = pageStartCount;
288        }
289
290        public void run() {
291            if (initialStartCount == pageStartCount && !isPageDone()) {
292                handler.removeMessages(MSG_TIMEOUT);
293                webView.stopLoading();
294                handler.postDelayed(pageDoneRunner, manualDelay);
295            }
296        }
297    }
298
299    class PageDoneRunner implements Runnable {
300
301        public void run() {
302            Log.v(LOGTAG, "Finishing URL: " + webView.getUrl());
303            pageLoadTime = System.currentTimeMillis() - startTime;
304            setPageDone(true);
305        }
306    }
307}
308