1/*
2 * Copyright (C) 2011 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.test.tilebenchmark;
18
19import android.content.Context;
20import android.os.CountDownTimer;
21import android.util.AttributeSet;
22import android.util.Log;
23import android.webkit.WebSettingsClassic;
24import android.webkit.WebView;
25import android.webkit.WebViewClassic;
26
27import java.util.ArrayList;
28
29import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
30import com.test.tilebenchmark.RunData.TileData;
31
32public class ProfiledWebView extends WebView implements WebViewClassic.PageSwapDelegate {
33    private static final String LOGTAG = "ProfiledWebView";
34
35    private int mSpeed;
36
37    private boolean mIsTesting = false;
38    private boolean mIsScrolling = false;
39    private ProfileCallback mCallback;
40    private long mContentInvalMillis;
41    private static final int LOAD_STALL_MILLIS = 2000; // nr of millis after load,
42                                                       // before test is forced
43
44    // ignore anim end events until this many millis after load
45    private static final long ANIM_SAFETY_THRESHOLD = 200;
46    private long mLoadTime;
47    private long mAnimationTime;
48
49    public ProfiledWebView(Context context) {
50        super(context);
51    }
52
53    public ProfiledWebView(Context context, AttributeSet attrs) {
54        super(context, attrs);
55    }
56
57    public ProfiledWebView(Context context, AttributeSet attrs, int defStyle) {
58        super(context, attrs, defStyle);
59    }
60
61    public ProfiledWebView(Context context, AttributeSet attrs, int defStyle,
62            boolean privateBrowsing) {
63        super(context, attrs, defStyle, privateBrowsing);
64    }
65
66    private class JavaScriptInterface {
67        Context mContext;
68
69        /** Instantiate the interface and set the context */
70        JavaScriptInterface(Context c) {
71            mContext = c;
72        }
73
74        public void animationComplete() {
75            mAnimationTime = System.currentTimeMillis();
76        }
77    }
78
79    public void init(Context c) {
80        WebSettingsClassic settings = getWebViewClassic().getSettings();
81        settings.setJavaScriptEnabled(true);
82        settings.setSupportZoom(true);
83        settings.setEnableSmoothTransition(true);
84        settings.setBuiltInZoomControls(true);
85        settings.setLoadWithOverviewMode(true);
86        settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
87        addJavascriptInterface(new JavaScriptInterface(c), "Android");
88        mAnimationTime = 0;
89        mLoadTime = 0;
90    }
91
92    public void setUseMinimalMemory(boolean minimal) {
93        WebSettingsClassic settings = getWebViewClassic().getSettings();
94        settings.setProperty("use_minimal_memory", minimal ? "true" : "false");
95    }
96
97    public void onPageFinished() {
98        mLoadTime = System.currentTimeMillis();
99    }
100
101    @Override
102    protected void onDraw(android.graphics.Canvas canvas) {
103        if (mIsTesting && mIsScrolling) {
104            if (canScrollVertically(1)) {
105                scrollBy(0, mSpeed);
106            } else {
107                stopScrollTest();
108                mIsScrolling = false;
109            }
110        }
111        super.onDraw(canvas);
112    }
113
114    /*
115     * Called once the page is loaded to start scrolling for evaluating tiles.
116     * If autoScrolling isn't set, stop must be called manually. Before
117     * scrolling, invalidate all content and redraw it, measuring time taken.
118     */
119    public void startScrollTest(ProfileCallback callback, boolean autoScrolling) {
120        mCallback = callback;
121        mIsTesting = false;
122        mIsScrolling = false;
123        WebSettingsClassic settings = getWebViewClassic().getSettings();
124        settings.setProperty("tree_updates", "0");
125
126
127        if (autoScrolling) {
128            // after a while, force it to start even if the pages haven't swapped
129            new CountDownTimer(LOAD_STALL_MILLIS, LOAD_STALL_MILLIS) {
130                @Override
131                public void onTick(long millisUntilFinished) {
132                }
133
134                @Override
135                public void onFinish() {
136                    // invalidate all content, and kick off redraw
137                    Log.d("ProfiledWebView",
138                            "kicking off test with callback registration, and tile discard...");
139                    getWebViewClassic().discardAllTextures();
140                    invalidate();
141                    mIsScrolling = true;
142                    mContentInvalMillis = System.currentTimeMillis();
143                }
144            }.start();
145        } else {
146            mIsTesting = true;
147            getWebViewClassic().tileProfilingStart();
148        }
149    }
150
151    /*
152     * Called after the manual contentInvalidateAll, after the tiles have all
153     * been redrawn.
154     * From PageSwapDelegate.
155     */
156    @Override
157    public void onPageSwapOccurred(boolean startAnim) {
158        if (!mIsTesting && mIsScrolling) {
159            // kick off testing
160            mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
161            Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis + "millis");
162            mIsTesting = true;
163            invalidate(); // ensure a redraw so that auto-scrolling can occur
164            getWebViewClassic().tileProfilingStart();
165        }
166    }
167
168    private double animFramerate() {
169        WebSettingsClassic settings = getWebViewClassic().getSettings();
170        String updatesString = settings.getProperty("tree_updates");
171        int updates = (updatesString == null) ? -1 : Integer.parseInt(updatesString);
172
173        long animationTime;
174        if (mAnimationTime == 0 || mAnimationTime - mLoadTime < ANIM_SAFETY_THRESHOLD) {
175            animationTime = System.currentTimeMillis() - mLoadTime;
176        } else {
177            animationTime = mAnimationTime - mLoadTime;
178        }
179
180        return updates * 1000.0 / animationTime;
181    }
182
183    public void setDoubleBuffering(boolean useDoubleBuffering) {
184        WebSettingsClassic settings = getWebViewClassic().getSettings();
185        settings.setProperty("use_double_buffering", useDoubleBuffering ? "true" : "false");
186    }
187
188    /*
189     * Called once the page has stopped scrolling
190     */
191    public void stopScrollTest() {
192        getWebViewClassic().tileProfilingStop();
193        mIsTesting = false;
194
195        if (mCallback == null) {
196            getWebViewClassic().tileProfilingClear();
197            return;
198        }
199
200        RunData data = new RunData(getWebViewClassic().tileProfilingNumFrames());
201        // record the time spent (before scrolling) rendering the page
202        data.singleStats.put(getResources().getString(R.string.render_millis),
203                (double)mContentInvalMillis);
204
205        // record framerate
206        double framerate = animFramerate();
207        Log.d(LOGTAG, "anim framerate was "+framerate);
208        data.singleStats.put(getResources().getString(R.string.animation_framerate),
209                framerate);
210
211        for (int frame = 0; frame < data.frames.length; frame++) {
212            data.frames[frame] = new TileData[
213                    getWebViewClassic().tileProfilingNumTilesInFrame(frame)];
214            for (int tile = 0; tile < data.frames[frame].length; tile++) {
215                int left = getWebViewClassic().tileProfilingGetInt(frame, tile, "left");
216                int top = getWebViewClassic().tileProfilingGetInt(frame, tile, "top");
217                int right = getWebViewClassic().tileProfilingGetInt(frame, tile, "right");
218                int bottom = getWebViewClassic().tileProfilingGetInt(frame, tile, "bottom");
219
220                boolean isReady = getWebViewClassic().tileProfilingGetInt(
221                        frame, tile, "isReady") == 1;
222                int level = getWebViewClassic().tileProfilingGetInt(frame, tile, "level");
223
224                float scale = getWebViewClassic().tileProfilingGetFloat(frame, tile, "scale");
225
226                data.frames[frame][tile] = data.new TileData(left, top, right, bottom,
227                        isReady, level, scale);
228            }
229        }
230        getWebViewClassic().tileProfilingClear();
231
232        mCallback.profileCallback(data);
233    }
234
235    @Override
236    public void loadUrl(String url) {
237        mAnimationTime = 0;
238        mLoadTime = 0;
239        if (!url.startsWith("http://") && !url.startsWith("file://")) {
240            url = "http://" + url;
241        }
242        super.loadUrl(url);
243    }
244
245    public void setAutoScrollSpeed(int speedInt) {
246        mSpeed = speedInt;
247    }
248
249    public WebViewClassic getWebViewClassic() {
250        return WebViewClassic.fromWebView(this);
251    }
252}
253