1// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.android_webview.test;
6
7import android.content.Context;
8import android.content.res.Configuration;
9import android.graphics.Canvas;
10import android.graphics.PixelFormat;
11import android.graphics.Rect;
12import android.opengl.GLSurfaceView;
13import android.os.Bundle;
14import android.view.KeyEvent;
15import android.view.MotionEvent;
16import android.view.SurfaceHolder;
17import android.view.View;
18import android.view.accessibility.AccessibilityEvent;
19import android.view.accessibility.AccessibilityNodeInfo;
20import android.view.accessibility.AccessibilityNodeProvider;
21import android.view.inputmethod.EditorInfo;
22import android.view.inputmethod.InputConnection;
23import android.widget.FrameLayout;
24
25import org.chromium.android_webview.AwContents;
26import org.chromium.android_webview.shell.DrawGL;
27import org.chromium.content.browser.ContentViewCore;
28
29import javax.microedition.khronos.egl.EGLConfig;
30import javax.microedition.khronos.opengles.GL10;
31
32/**
33 * A View used for testing the AwContents internals.
34 *
35 * This class takes the place android.webkit.WebView would have in the production configuration.
36 */
37public class AwTestContainerView extends FrameLayout {
38    private AwContents mAwContents;
39    private AwContents.NativeGLDelegate mNativeGLDelegate;
40    private AwContents.InternalAccessDelegate mInternalAccessDelegate;
41
42    HardwareView mHardwareView = null;
43    private boolean mAttachedContents = false;
44
45    private class HardwareView extends GLSurfaceView {
46        private static final int MODE_DRAW = 0;
47        private static final int MODE_PROCESS = 1;
48        private static final int MODE_PROCESS_NO_CONTEXT = 2;
49        private static final int MODE_SYNC = 3;
50
51        // mSyncLock is used to synchronized requestRender on the UI thread
52        // and drawGL on the rendering thread. The variables following
53        // are protected by it.
54        private final Object mSyncLock = new Object();
55        private boolean mFunctorAttached = false;
56        private boolean mNeedsProcessGL = false;
57        private boolean mNeedsDrawGL = false;
58        private boolean mWaitForCompletion = false;
59        private int mLastScrollX = 0;
60        private int mLastScrollY = 0;
61
62        private int mCommittedScrollX = 0;
63        private int mCommittedScrollY = 0;
64
65        private boolean mHaveSurface = false;
66        private Runnable mReadyToRenderCallback = null;
67
68        private long mDrawGL = 0;
69        private long mViewContext = 0;
70
71        public HardwareView(Context context) {
72            super(context);
73            setEGLContextClientVersion(2); // GLES2
74            getHolder().setFormat(PixelFormat.OPAQUE);
75            setPreserveEGLContextOnPause(true);
76            setRenderer(new Renderer() {
77                private int mWidth = 0;
78                private int mHeight = 0;
79
80                @Override
81                public void onDrawFrame(GL10 gl) {
82                    HardwareView.this.drawGL(mWidth, mHeight);
83                }
84
85                @Override
86                public void onSurfaceChanged(GL10 gl, int width, int height) {
87                    gl.glViewport(0, 0, width, height);
88                    gl.glScissor(0, 0, width, height);
89                    mWidth = width;
90                    mHeight = height;
91                }
92
93                @Override
94                public void onSurfaceCreated(GL10 gl, EGLConfig config) {
95                }
96            });
97
98            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
99        }
100
101        public void initialize(long drawGL, long viewContext) {
102            mDrawGL = drawGL;
103            mViewContext = viewContext;
104        }
105
106        public boolean isReadyToRender() {
107            return mHaveSurface;
108        }
109
110        public void setReadyToRenderCallback(Runnable runner) {
111            assert !isReadyToRender() || runner == null;
112            mReadyToRenderCallback = runner;
113        }
114
115        @Override
116        public void surfaceCreated(SurfaceHolder holder) {
117            boolean didHaveSurface = mHaveSurface;
118            mHaveSurface = true;
119            if (!didHaveSurface && mReadyToRenderCallback != null) {
120                mReadyToRenderCallback.run();
121                mReadyToRenderCallback = null;
122            }
123            super.surfaceCreated(holder);
124        }
125
126        @Override
127        public void surfaceDestroyed(SurfaceHolder holder) {
128            mHaveSurface = false;
129            super.surfaceDestroyed(holder);
130        }
131
132        public void updateScroll(int x, int y) {
133            synchronized (mSyncLock) {
134                mLastScrollX = x;
135                mLastScrollY = y;
136            }
137        }
138
139        public void detachGLFunctor() {
140            synchronized (mSyncLock) {
141                mFunctorAttached = false;
142                mNeedsProcessGL = false;
143                mNeedsDrawGL = false;
144                mWaitForCompletion = false;
145            }
146        }
147
148        public void requestRender(Canvas canvas, boolean waitForCompletion) {
149            synchronized (mSyncLock) {
150                super.requestRender();
151                mFunctorAttached = true;
152                mWaitForCompletion = waitForCompletion;
153                if (canvas == null) {
154                    mNeedsProcessGL = true;
155                } else {
156                    mNeedsDrawGL = true;
157                    if (!waitForCompletion) {
158                        // Wait until SYNC is complete only.
159                        // Do this every time there was a new frame.
160                        try {
161                            while (mNeedsDrawGL) {
162                                mSyncLock.wait();
163                            }
164                        } catch (InterruptedException e) {
165                            // ...
166                        }
167                    }
168                }
169                if (waitForCompletion) {
170                    try {
171                        while (mWaitForCompletion) {
172                            mSyncLock.wait();
173                        }
174                    } catch (InterruptedException e) {
175                        // ...
176                    }
177                }
178            }
179        }
180
181        public void drawGL(int width, int height) {
182            final boolean draw;
183            final boolean process;
184            final boolean waitForCompletion;
185
186            synchronized (mSyncLock) {
187                if (!mFunctorAttached) {
188                    mSyncLock.notifyAll();
189                    return;
190                }
191
192                draw = mNeedsDrawGL;
193                process = mNeedsProcessGL;
194                waitForCompletion = mWaitForCompletion;
195
196                if (draw) {
197                    DrawGL.drawGL(mDrawGL, mViewContext, width, height, 0, 0, MODE_SYNC);
198                    mCommittedScrollX = mLastScrollX;
199                    mCommittedScrollY = mLastScrollY;
200                }
201                mNeedsDrawGL = false;
202                mNeedsProcessGL = false;
203                if (!waitForCompletion) {
204                    mSyncLock.notifyAll();
205                }
206            }
207            if (draw) {
208                DrawGL.drawGL(mDrawGL, mViewContext, width, height,
209                        mCommittedScrollX, mCommittedScrollY, MODE_DRAW);
210            } else if (process) {
211                DrawGL.drawGL(mDrawGL, mViewContext, width, height, 0, 0, MODE_PROCESS);
212            }
213
214            if (waitForCompletion) {
215                synchronized (mSyncLock) {
216                    mWaitForCompletion = false;
217                    mSyncLock.notifyAll();
218                }
219            }
220        }
221    }
222
223    private static boolean sCreatedOnce = false;
224    private HardwareView createHardwareViewOnlyOnce(Context context) {
225        if (sCreatedOnce) return null;
226        sCreatedOnce = true;
227        return new HardwareView(context);
228    }
229
230    public AwTestContainerView(Context context, boolean hardwareAccelerated) {
231        super(context);
232        if (hardwareAccelerated) {
233            mHardwareView = createHardwareViewOnlyOnce(context);
234        }
235        if (mHardwareView != null) {
236            addView(mHardwareView,
237                    new FrameLayout.LayoutParams(
238                        FrameLayout.LayoutParams.MATCH_PARENT,
239                        FrameLayout.LayoutParams.MATCH_PARENT));
240        } else {
241          setLayerType(LAYER_TYPE_SOFTWARE, null);
242        }
243        mNativeGLDelegate = new NativeGLDelegate();
244        mInternalAccessDelegate = new InternalAccessAdapter();
245        setOverScrollMode(View.OVER_SCROLL_ALWAYS);
246        setFocusable(true);
247        setFocusableInTouchMode(true);
248    }
249
250    public void initialize(AwContents awContents) {
251        mAwContents = awContents;
252        if (mHardwareView != null) {
253            mHardwareView.initialize(
254                    mAwContents.getAwDrawGLFunction(), mAwContents.getAwDrawGLViewContext());
255        }
256    }
257
258    public ContentViewCore getContentViewCore() {
259        return mAwContents.getContentViewCore();
260    }
261
262    public AwContents getAwContents() {
263        return mAwContents;
264    }
265
266    public AwContents.NativeGLDelegate getNativeGLDelegate() {
267        return mNativeGLDelegate;
268    }
269
270    public AwContents.InternalAccessDelegate getInternalAccessDelegate() {
271        return mInternalAccessDelegate;
272    }
273
274    public void destroy() {
275        mAwContents.destroy();
276    }
277
278    @Override
279    public void onConfigurationChanged(Configuration newConfig) {
280        super.onConfigurationChanged(newConfig);
281        mAwContents.onConfigurationChanged(newConfig);
282    }
283
284    @Override
285    public void onAttachedToWindow() {
286        super.onAttachedToWindow();
287        if (mHardwareView == null || mHardwareView.isReadyToRender()) {
288            mAwContents.onAttachedToWindow();
289            mAttachedContents = true;
290        } else {
291            mHardwareView.setReadyToRenderCallback(new Runnable() {
292                public void run() {
293                    assert !mAttachedContents;
294                    mAwContents.onAttachedToWindow();
295                    mAttachedContents = true;
296                }
297            });
298        }
299    }
300
301    @Override
302    public void onDetachedFromWindow() {
303        super.onDetachedFromWindow();
304        mAwContents.onDetachedFromWindow();
305        if (mHardwareView != null) {
306            mHardwareView.setReadyToRenderCallback(null);
307        }
308        mAttachedContents = false;
309    }
310
311    @Override
312    public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
313        super.onFocusChanged(focused, direction, previouslyFocusedRect);
314        mAwContents.onFocusChanged(focused, direction, previouslyFocusedRect);
315    }
316
317    @Override
318    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
319        return mAwContents.onCreateInputConnection(outAttrs);
320    }
321
322    @Override
323    public boolean onKeyUp(int keyCode, KeyEvent event) {
324        return mAwContents.onKeyUp(keyCode, event);
325    }
326
327    @Override
328    public boolean dispatchKeyEvent(KeyEvent event) {
329        return mAwContents.dispatchKeyEvent(event);
330    }
331
332    @Override
333    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
334        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
335        mAwContents.onMeasure(widthMeasureSpec, heightMeasureSpec);
336    }
337
338    @Override
339    public void onSizeChanged(int w, int h, int ow, int oh) {
340        super.onSizeChanged(w, h, ow, oh);
341        mAwContents.onSizeChanged(w, h, ow, oh);
342    }
343
344    @Override
345    public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
346        mAwContents.onContainerViewOverScrolled(scrollX, scrollY, clampedX, clampedY);
347    }
348
349    @Override
350    public void onScrollChanged(int l, int t, int oldl, int oldt) {
351        super.onScrollChanged(l, t, oldl, oldt);
352        if (mAwContents != null) {
353            mAwContents.onContainerViewScrollChanged(l, t, oldl, oldt);
354        }
355    }
356
357    @Override
358    public void computeScroll() {
359        mAwContents.computeScroll();
360    }
361
362    @Override
363    public void onVisibilityChanged(View changedView, int visibility) {
364        super.onVisibilityChanged(changedView, visibility);
365        mAwContents.onVisibilityChanged(changedView, visibility);
366    }
367
368    @Override
369    public void onWindowVisibilityChanged(int visibility) {
370        super.onWindowVisibilityChanged(visibility);
371        mAwContents.onWindowVisibilityChanged(visibility);
372    }
373
374    @Override
375    public boolean onTouchEvent(MotionEvent ev) {
376        super.onTouchEvent(ev);
377        return mAwContents.onTouchEvent(ev);
378    }
379
380    @Override
381    public void onDraw(Canvas canvas) {
382        if (mHardwareView != null) {
383            mHardwareView.updateScroll(getScrollX(), getScrollY());
384        }
385        mAwContents.onDraw(canvas);
386        super.onDraw(canvas);
387    }
388
389    @Override
390    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
391        AccessibilityNodeProvider provider =
392            mAwContents.getAccessibilityNodeProvider();
393        return provider == null ? super.getAccessibilityNodeProvider() : provider;
394    }
395
396    @Override
397    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
398        super.onInitializeAccessibilityNodeInfo(info);
399        info.setClassName(AwContents.class.getName());
400        mAwContents.onInitializeAccessibilityNodeInfo(info);
401    }
402
403    @Override
404    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
405        super.onInitializeAccessibilityEvent(event);
406        event.setClassName(AwContents.class.getName());
407        mAwContents.onInitializeAccessibilityEvent(event);
408    }
409
410    @Override
411    public boolean performAccessibilityAction(int action, Bundle arguments) {
412        return mAwContents.performAccessibilityAction(action, arguments);
413    }
414
415    private class NativeGLDelegate implements AwContents.NativeGLDelegate {
416        @Override
417        public boolean requestDrawGL(Canvas canvas, boolean waitForCompletion,
418                View containerview) {
419            if (mHardwareView == null) return false;
420            mHardwareView.requestRender(canvas, waitForCompletion);
421            return true;
422        }
423
424        @Override
425        public void detachGLFunctor() {
426            if (mHardwareView != null) mHardwareView.detachGLFunctor();
427        }
428    }
429
430    // TODO: AwContents could define a generic class that holds an implementation similar to
431    // the one below.
432    private class InternalAccessAdapter implements AwContents.InternalAccessDelegate {
433
434        @Override
435        public boolean drawChild(Canvas canvas, View child, long drawingTime) {
436            return AwTestContainerView.super.drawChild(canvas, child, drawingTime);
437        }
438
439        @Override
440        public boolean super_onKeyUp(int keyCode, KeyEvent event) {
441            return AwTestContainerView.super.onKeyUp(keyCode, event);
442        }
443
444        @Override
445        public boolean super_dispatchKeyEventPreIme(KeyEvent event) {
446            return AwTestContainerView.super.dispatchKeyEventPreIme(event);
447        }
448
449        @Override
450        public boolean super_dispatchKeyEvent(KeyEvent event) {
451            return AwTestContainerView.super.dispatchKeyEvent(event);
452        }
453
454        @Override
455        public boolean super_onGenericMotionEvent(MotionEvent event) {
456            return AwTestContainerView.super.onGenericMotionEvent(event);
457        }
458
459        @Override
460        public void super_onConfigurationChanged(Configuration newConfig) {
461            AwTestContainerView.super.onConfigurationChanged(newConfig);
462        }
463
464        @Override
465        public void super_scrollTo(int scrollX, int scrollY) {
466            // We're intentionally not calling super.scrollTo here to make testing easier.
467            AwTestContainerView.this.scrollTo(scrollX, scrollY);
468            if (mHardwareView != null) {
469                // Undo the scroll that will be applied because of mHardwareView
470                // being a child of |this|.
471                mHardwareView.setTranslationX(scrollX);
472                mHardwareView.setTranslationY(scrollY);
473            }
474        }
475
476        @Override
477        public void overScrollBy(int deltaX, int deltaY,
478                int scrollX, int scrollY,
479                int scrollRangeX, int scrollRangeY,
480                int maxOverScrollX, int maxOverScrollY,
481                boolean isTouchEvent) {
482            // We're intentionally not calling super.scrollTo here to make testing easier.
483            AwTestContainerView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY,
484                     scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
485        }
486
487        @Override
488        public void onScrollChanged(int l, int t, int oldl, int oldt) {
489            AwTestContainerView.super.onScrollChanged(l, t, oldl, oldt);
490        }
491
492        @Override
493        public boolean awakenScrollBars() {
494            return AwTestContainerView.super.awakenScrollBars();
495        }
496
497        @Override
498        public boolean super_awakenScrollBars(int startDelay, boolean invalidate) {
499            return AwTestContainerView.super.awakenScrollBars(startDelay, invalidate);
500        }
501
502        @Override
503        public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
504            AwTestContainerView.super.setMeasuredDimension(measuredWidth, measuredHeight);
505        }
506
507        @Override
508        public int super_getScrollBarStyle() {
509            return AwTestContainerView.super.getScrollBarStyle();
510        }
511    }
512}
513