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 
5 package org.chromium.android_webview.test;
6 
7 import android.annotation.TargetApi;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.res.Configuration;
11 import android.graphics.Canvas;
12 import android.graphics.Color;
13 import android.graphics.PixelFormat;
14 import android.graphics.Rect;
15 import android.opengl.GLSurfaceView;
16 import android.os.Build;
17 import android.os.Bundle;
18 import android.view.DragEvent;
19 import android.view.KeyEvent;
20 import android.view.MotionEvent;
21 import android.view.SurfaceHolder;
22 import android.view.View;
23 import android.view.accessibility.AccessibilityNodeProvider;
24 import android.view.inputmethod.EditorInfo;
25 import android.view.inputmethod.InputConnection;
26 import android.widget.FrameLayout;
27 
28 import org.chromium.android_webview.AwContents;
29 import org.chromium.android_webview.gfx.AwDrawFnImpl;
30 import org.chromium.android_webview.shell.DrawFn;
31 import org.chromium.base.Callback;
32 import org.chromium.content_public.browser.WebContents;
33 
34 import java.nio.ByteBuffer;
35 
36 import javax.microedition.khronos.egl.EGLConfig;
37 import javax.microedition.khronos.opengles.GL10;
38 
39 /**
40  * A View used for testing the AwContents internals.
41  *
42  * This class takes the place android.webkit.WebView would have in the production configuration.
43  */
44 public class AwTestContainerView extends FrameLayout {
45     private AwContents mAwContents;
46     private AwContents.InternalAccessDelegate mInternalAccessDelegate;
47 
48     private HardwareView mHardwareView;
49     private boolean mAttachedContents;
50 
51     private Rect mWindowVisibleDisplayFrameOverride;
52 
53     private class HardwareView extends GLSurfaceView {
54         private static final int MODE_DRAW = 0;
55         private static final int MODE_PROCESS = 1;
56         private static final int MODE_PROCESS_NO_CONTEXT = 2;
57         private static final int MODE_SYNC = 3;
58 
59         // mSyncLock is used to synchronized requestRender on the UI thread
60         // and drawGL on the rendering thread. The variables following
61         // are protected by it.
62         private final Object mSyncLock = new Object();
63         private int mFunctor;
64         private int mLastDrawnFunctor;
65         private boolean mSyncDone;
66         private boolean mPendingDestroy;
67         private int mLastScrollX;
68         private int mLastScrollY;
69         private Callback<int[]> mQuadrantReadbackCallback;
70 
71         // Only used by drawGL on render thread to store the value of scroll offsets at most recent
72         // sync for subsequent draws.
73         private int mCommittedScrollX;
74         private int mCommittedScrollY;
75 
76         private boolean mHaveSurface;
77         private Runnable mReadyToRenderCallback;
78         private Runnable mReadyToDetachCallback;
79 
HardwareView(Context context)80         public HardwareView(Context context) {
81             super(context);
82             setEGLContextClientVersion(2); // GLES2
83             getHolder().setFormat(PixelFormat.OPAQUE);
84             setPreserveEGLContextOnPause(true);
85             setRenderer(new Renderer() {
86                 private int mWidth;
87                 private int mHeight;
88 
89                 @Override
90                 public void onDrawFrame(GL10 gl) {
91                     HardwareView.this.onDrawFrame(gl, mWidth, mHeight);
92                 }
93 
94                 @Override
95                 public void onSurfaceChanged(GL10 gl, int width, int height) {
96                     gl.glViewport(0, 0, width, height);
97                     gl.glScissor(0, 0, width, height);
98                     mWidth = width;
99                     mHeight = height;
100                 }
101 
102                 @Override
103                 public void onSurfaceCreated(GL10 gl, EGLConfig config) {}
104             });
105 
106             setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
107         }
108 
readbackQuadrantColors(Callback<int[]> callback)109         public void readbackQuadrantColors(Callback<int[]> callback) {
110             synchronized (mSyncLock) {
111                 mQuadrantReadbackCallback = callback;
112             }
113             super.requestRender();
114         }
115 
isReadyToRender()116         public boolean isReadyToRender() {
117             return mHaveSurface;
118         }
119 
setReadyToRenderCallback(Runnable runner)120         public void setReadyToRenderCallback(Runnable runner) {
121             assert !isReadyToRender() || runner == null;
122             mReadyToRenderCallback = runner;
123         }
124 
setReadyToDetachCallback(Runnable runner)125         public void setReadyToDetachCallback(Runnable runner) {
126             mReadyToDetachCallback = runner;
127         }
128 
129         @Override
surfaceCreated(SurfaceHolder holder)130         public void surfaceCreated(SurfaceHolder holder) {
131             mHaveSurface = true;
132             if (mReadyToRenderCallback != null) {
133                 mReadyToRenderCallback.run();
134                 mReadyToRenderCallback = null;
135             }
136             super.surfaceCreated(holder);
137         }
138 
139         @Override
surfaceDestroyed(SurfaceHolder holder)140         public void surfaceDestroyed(SurfaceHolder holder) {
141             mHaveSurface = false;
142             if (mReadyToDetachCallback != null) {
143                 mReadyToDetachCallback.run();
144                 mReadyToDetachCallback = null;
145             }
146             super.surfaceDestroyed(holder);
147         }
148 
updateScroll(int x, int y)149         public void updateScroll(int x, int y) {
150             synchronized (mSyncLock) {
151                 mLastScrollX = x;
152                 mLastScrollY = y;
153             }
154         }
155 
awContentsDetached()156         public void awContentsDetached() {
157             synchronized (mSyncLock) {
158                 super.requestRender();
159                 assert !mPendingDestroy;
160                 mPendingDestroy = true;
161                 try {
162                     while (!mPendingDestroy) {
163                         mSyncLock.wait();
164                     }
165                 } catch (InterruptedException e) {
166                     // ...
167                 }
168             }
169         }
170 
drawWebViewFunctor(int functor)171         public void drawWebViewFunctor(int functor) {
172             synchronized (mSyncLock) {
173                 super.requestRender();
174                 assert mFunctor == 0;
175                 mFunctor = functor;
176                 mSyncDone = false;
177                 try {
178                     while (!mSyncDone) {
179                         mSyncLock.wait();
180                     }
181                 } catch (InterruptedException e) {
182                     // ...
183                 }
184             }
185         }
186 
onDrawFrame(GL10 gl, int width, int height)187         public void onDrawFrame(GL10 gl, int width, int height) {
188             int functor;
189             int scrollX;
190             int scrollY;
191             synchronized (mSyncLock) {
192                 scrollX = mLastScrollX;
193                 scrollY = mLastScrollY;
194 
195                 if (mFunctor != 0) {
196                     assert !mSyncDone;
197                     functor = mFunctor;
198                     mLastDrawnFunctor = mFunctor;
199                     mFunctor = 0;
200                     DrawFn.sync(functor, false);
201                     mSyncDone = true;
202                     mSyncLock.notifyAll();
203                 } else {
204                     functor = mLastDrawnFunctor;
205                     if (mPendingDestroy) {
206                         DrawFn.destroyReleased();
207                         mPendingDestroy = false;
208                         mLastDrawnFunctor = 0;
209                         mSyncLock.notifyAll();
210                         return;
211                     }
212                 }
213             }
214             if (functor != 0) {
215                 DrawFn.drawGL(functor, width, height, scrollX, scrollY);
216 
217                 Callback<int[]> quadrantReadbackCallback = null;
218                 synchronized (mSyncLock) {
219                     if (mQuadrantReadbackCallback != null) {
220                         quadrantReadbackCallback = mQuadrantReadbackCallback;
221                         mQuadrantReadbackCallback = null;
222                     }
223                 }
224                 if (quadrantReadbackCallback != null) {
225                     int quadrantColors[] = new int[4];
226                     int quarterWidth = width / 4;
227                     int quarterHeight = height / 4;
228                     ByteBuffer buffer = ByteBuffer.allocate(4);
229                     gl.glReadPixels(quarterWidth, quarterHeight * 3, 1, 1, GL10.GL_RGBA,
230                             GL10.GL_UNSIGNED_BYTE, buffer);
231                     quadrantColors[0] = readbackToColor(buffer);
232                     gl.glReadPixels(quarterWidth * 3, quarterHeight * 3, 1, 1, GL10.GL_RGBA,
233                             GL10.GL_UNSIGNED_BYTE, buffer);
234                     quadrantColors[1] = readbackToColor(buffer);
235                     gl.glReadPixels(quarterWidth, quarterHeight, 1, 1, GL10.GL_RGBA,
236                             GL10.GL_UNSIGNED_BYTE, buffer);
237                     quadrantColors[2] = readbackToColor(buffer);
238                     gl.glReadPixels(quarterWidth * 3, quarterHeight, 1, 1, GL10.GL_RGBA,
239                             GL10.GL_UNSIGNED_BYTE, buffer);
240                     quadrantColors[3] = readbackToColor(buffer);
241                     quadrantReadbackCallback.onResult(quadrantColors);
242                 }
243             }
244         }
245 
readbackToColor(ByteBuffer buffer)246         private int readbackToColor(ByteBuffer buffer) {
247             return Color.argb(buffer.get(3) & 0xff, buffer.get(0) & 0xff, buffer.get(1) & 0xff,
248                     buffer.get(2) & 0xff);
249         }
250     }
251 
252     private static boolean sCreatedOnce;
createHardwareViewOnlyOnce(Context context)253     private HardwareView createHardwareViewOnlyOnce(Context context) {
254         if (sCreatedOnce) return null;
255         sCreatedOnce = true;
256         return new HardwareView(context);
257     }
258 
AwTestContainerView(Context context, boolean allowHardwareAcceleration)259     public AwTestContainerView(Context context, boolean allowHardwareAcceleration) {
260         super(context);
261         if (allowHardwareAcceleration) {
262             mHardwareView = createHardwareViewOnlyOnce(context);
263         }
264         if (isBackedByHardwareView()) {
265             addView(mHardwareView,
266                     new FrameLayout.LayoutParams(
267                         FrameLayout.LayoutParams.MATCH_PARENT,
268                         FrameLayout.LayoutParams.MATCH_PARENT));
269         } else {
270             setLayerType(LAYER_TYPE_SOFTWARE, null);
271         }
272         mInternalAccessDelegate = new InternalAccessAdapter();
273         setOverScrollMode(View.OVER_SCROLL_ALWAYS);
274         setFocusable(true);
275         setFocusableInTouchMode(true);
276     }
277 
initialize(AwContents awContents)278     public void initialize(AwContents awContents) {
279         mAwContents = awContents;
280         if (isBackedByHardwareView()) {
281             AwDrawFnImpl.setDrawFnFunctionTable(DrawFn.getDrawFnFunctionTable());
282         }
283     }
284 
setWindowVisibleDisplayFrameOverride(Rect rect)285     public void setWindowVisibleDisplayFrameOverride(Rect rect) {
286         mWindowVisibleDisplayFrameOverride = rect;
287     }
288 
289     @Override
getWindowVisibleDisplayFrame(Rect outRect)290     public void getWindowVisibleDisplayFrame(Rect outRect) {
291         if (mWindowVisibleDisplayFrameOverride != null) {
292             outRect.set(mWindowVisibleDisplayFrameOverride);
293         } else {
294             super.getWindowVisibleDisplayFrame(outRect);
295         }
296     }
297 
isBackedByHardwareView()298     public boolean isBackedByHardwareView() {
299         return mHardwareView != null;
300     }
301 
302     /**
303      * Use glReadPixels to get 4 pixels from center of 4 quadrants. Result is in row-major order.
304      */
readbackQuadrantColors(Callback<int[]> callback)305     public void readbackQuadrantColors(Callback<int[]> callback) {
306         assert isBackedByHardwareView();
307         mHardwareView.readbackQuadrantColors(callback);
308     }
309 
getWebContents()310     public WebContents getWebContents() {
311         return mAwContents.getWebContents();
312     }
313 
getAwContents()314     public AwContents getAwContents() {
315         return mAwContents;
316     }
317 
getNativeDrawFunctorFactory()318     public AwContents.NativeDrawFunctorFactory getNativeDrawFunctorFactory() {
319         return new NativeDrawFunctorFactory();
320     }
321 
getInternalAccessDelegate()322     public AwContents.InternalAccessDelegate getInternalAccessDelegate() {
323         return mInternalAccessDelegate;
324     }
325 
destroy()326     public void destroy() {
327         mAwContents.destroy();
328     }
329 
330     @Override
onConfigurationChanged(Configuration newConfig)331     public void onConfigurationChanged(Configuration newConfig) {
332         super.onConfigurationChanged(newConfig);
333         mAwContents.onConfigurationChanged(newConfig);
334     }
335 
attachedContentsInternal()336     private void attachedContentsInternal() {
337         assert !mAttachedContents;
338         mAwContents.onAttachedToWindow();
339         mAttachedContents = true;
340     }
341 
detachedContentsInternal()342     private void detachedContentsInternal() {
343         assert mAttachedContents;
344         mAwContents.onDetachedFromWindow();
345         mAttachedContents = false;
346         if (mHardwareView != null) {
347             mHardwareView.awContentsDetached();
348         }
349     }
350 
351     @Override
onAttachedToWindow()352     public void onAttachedToWindow() {
353         super.onAttachedToWindow();
354         if (mHardwareView == null || mHardwareView.isReadyToRender()) {
355             attachedContentsInternal();
356         } else {
357             mHardwareView.setReadyToRenderCallback(() -> attachedContentsInternal());
358         }
359 
360         if (mHardwareView != null) {
361             mHardwareView.setReadyToDetachCallback(() -> detachedContentsInternal());
362         }
363     }
364 
365     @Override
onDetachedFromWindow()366     public void onDetachedFromWindow() {
367         super.onDetachedFromWindow();
368         if (mHardwareView == null || mHardwareView.isReadyToRender()) {
369             detachedContentsInternal();
370 
371             if (mHardwareView != null) {
372                 mHardwareView.setReadyToRenderCallback(null);
373                 mHardwareView.setReadyToDetachCallback(null);
374             }
375         }
376     }
377 
378     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)379     public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
380         super.onFocusChanged(focused, direction, previouslyFocusedRect);
381         mAwContents.onFocusChanged(focused, direction, previouslyFocusedRect);
382     }
383 
384     @Override
onCreateInputConnection(EditorInfo outAttrs)385     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
386         return mAwContents.onCreateInputConnection(outAttrs);
387     }
388 
389     @Override
onKeyUp(int keyCode, KeyEvent event)390     public boolean onKeyUp(int keyCode, KeyEvent event) {
391         return mAwContents.onKeyUp(keyCode, event);
392     }
393 
394     @Override
dispatchKeyEvent(KeyEvent event)395     public boolean dispatchKeyEvent(KeyEvent event) {
396         return mAwContents.dispatchKeyEvent(event);
397     }
398 
399     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)400     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
401         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
402         mAwContents.onMeasure(widthMeasureSpec, heightMeasureSpec);
403     }
404 
405     @Override
onSizeChanged(int w, int h, int ow, int oh)406     public void onSizeChanged(int w, int h, int ow, int oh) {
407         super.onSizeChanged(w, h, ow, oh);
408         mAwContents.onSizeChanged(w, h, ow, oh);
409     }
410 
411     @Override
onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)412     public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
413         mAwContents.onContainerViewOverScrolled(scrollX, scrollY, clampedX, clampedY);
414     }
415 
416     @Override
onScrollChanged(int l, int t, int oldl, int oldt)417     public void onScrollChanged(int l, int t, int oldl, int oldt) {
418         super.onScrollChanged(l, t, oldl, oldt);
419         if (mAwContents != null) {
420             mAwContents.onContainerViewScrollChanged(l, t, oldl, oldt);
421         }
422     }
423 
424     @Override
computeScroll()425     public void computeScroll() {
426         mAwContents.computeScroll();
427     }
428 
429     @Override
onVisibilityChanged(View changedView, int visibility)430     public void onVisibilityChanged(View changedView, int visibility) {
431         super.onVisibilityChanged(changedView, visibility);
432         mAwContents.onVisibilityChanged(changedView, visibility);
433     }
434 
435     @Override
onWindowVisibilityChanged(int visibility)436     public void onWindowVisibilityChanged(int visibility) {
437         super.onWindowVisibilityChanged(visibility);
438         mAwContents.onWindowVisibilityChanged(visibility);
439     }
440 
441     @Override
onTouchEvent(MotionEvent ev)442     public boolean onTouchEvent(MotionEvent ev) {
443         super.onTouchEvent(ev);
444         return mAwContents.onTouchEvent(ev);
445     }
446 
447     @Override
onGenericMotionEvent(MotionEvent ev)448     public boolean onGenericMotionEvent(MotionEvent ev) {
449         super.onGenericMotionEvent(ev);
450         return mAwContents.onGenericMotionEvent(ev);
451     }
452 
453     @Override
onHoverEvent(MotionEvent ev)454     public boolean onHoverEvent(MotionEvent ev) {
455         super.onHoverEvent(ev);
456         return mAwContents.onHoverEvent(ev);
457     }
458 
459     @Override
onDraw(Canvas canvas)460     public void onDraw(Canvas canvas) {
461         if (isBackedByHardwareView()) {
462             mHardwareView.updateScroll(getScrollX(), getScrollY());
463         }
464         mAwContents.onDraw(canvas);
465         super.onDraw(canvas);
466     }
467 
468     @Override
getAccessibilityNodeProvider()469     public AccessibilityNodeProvider getAccessibilityNodeProvider() {
470         AccessibilityNodeProvider provider =
471                 mAwContents.getAccessibilityNodeProvider();
472         return provider == null ? super.getAccessibilityNodeProvider() : provider;
473     }
474 
475     @Override
performAccessibilityAction(int action, Bundle arguments)476     public boolean performAccessibilityAction(int action, Bundle arguments) {
477         return mAwContents.performAccessibilityAction(action, arguments);
478     }
479 
480     @Override
481     @TargetApi(Build.VERSION_CODES.N)
onDragEvent(DragEvent event)482     public boolean onDragEvent(DragEvent event) {
483         return mAwContents.onDragEvent(event);
484     }
485 
486     private class NativeDrawFunctorFactory implements AwContents.NativeDrawFunctorFactory {
487         @Override
createGLFunctor(long context)488         public AwContents.NativeDrawGLFunctor createGLFunctor(long context) {
489             return null;
490         }
491 
492         @Override
getDrawFnAccess()493         public AwDrawFnImpl.DrawFnAccess getDrawFnAccess() {
494             return new DrawFnAccess();
495         }
496     }
497 
498     private class DrawFnAccess implements AwDrawFnImpl.DrawFnAccess {
499         @Override
drawWebViewFunctor(Canvas canvas, int functor)500         public void drawWebViewFunctor(Canvas canvas, int functor) {
501             assert isBackedByHardwareView();
502             mHardwareView.drawWebViewFunctor(functor);
503         }
504     }
505 
506     // TODO: AwContents could define a generic class that holds an implementation similar to
507     // the one below.
508     private class InternalAccessAdapter implements AwContents.InternalAccessDelegate {
509 
510         @Override
super_onKeyUp(int keyCode, KeyEvent event)511         public boolean super_onKeyUp(int keyCode, KeyEvent event) {
512             return AwTestContainerView.super.onKeyUp(keyCode, event);
513         }
514 
515         @Override
super_dispatchKeyEvent(KeyEvent event)516         public boolean super_dispatchKeyEvent(KeyEvent event) {
517             return AwTestContainerView.super.dispatchKeyEvent(event);
518         }
519 
520         @Override
super_onGenericMotionEvent(MotionEvent event)521         public boolean super_onGenericMotionEvent(MotionEvent event) {
522             return AwTestContainerView.super.onGenericMotionEvent(event);
523         }
524 
525         @Override
super_onConfigurationChanged(Configuration newConfig)526         public void super_onConfigurationChanged(Configuration newConfig) {
527             AwTestContainerView.super.onConfigurationChanged(newConfig);
528         }
529 
530         @Override
super_scrollTo(int scrollX, int scrollY)531         public void super_scrollTo(int scrollX, int scrollY) {
532             // We're intentionally not calling super.scrollTo here to make testing easier.
533             AwTestContainerView.this.scrollTo(scrollX, scrollY);
534             if (isBackedByHardwareView()) {
535                 // Undo the scroll that will be applied because of mHardwareView
536                 // being a child of |this|.
537                 mHardwareView.setTranslationX(scrollX);
538                 mHardwareView.setTranslationY(scrollY);
539             }
540         }
541 
542         @Override
overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)543         public void overScrollBy(int deltaX, int deltaY,
544                 int scrollX, int scrollY,
545                 int scrollRangeX, int scrollRangeY,
546                 int maxOverScrollX, int maxOverScrollY,
547                 boolean isTouchEvent) {
548             // We're intentionally not calling super.scrollTo here to make testing easier.
549             AwTestContainerView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY,
550                      scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
551         }
552 
553         @Override
onScrollChanged(int l, int t, int oldl, int oldt)554         public void onScrollChanged(int l, int t, int oldl, int oldt) {
555             AwTestContainerView.super.onScrollChanged(l, t, oldl, oldt);
556         }
557 
558         @Override
setMeasuredDimension(int measuredWidth, int measuredHeight)559         public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
560             AwTestContainerView.super.setMeasuredDimension(measuredWidth, measuredHeight);
561         }
562 
563         @Override
super_getScrollBarStyle()564         public int super_getScrollBarStyle() {
565             return AwTestContainerView.super.getScrollBarStyle();
566         }
567 
568         @Override
super_startActivityForResult(Intent intent, int requestCode)569         public void super_startActivityForResult(Intent intent, int requestCode) {}
570     }
571 }
572