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