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.content.Context; 8 import android.content.res.Configuration; 9 import android.graphics.Canvas; 10 import android.graphics.PixelFormat; 11 import android.graphics.Rect; 12 import android.opengl.GLSurfaceView; 13 import android.os.Bundle; 14 import android.view.KeyEvent; 15 import android.view.MotionEvent; 16 import android.view.SurfaceHolder; 17 import android.view.View; 18 import android.view.accessibility.AccessibilityEvent; 19 import android.view.accessibility.AccessibilityNodeInfo; 20 import android.view.accessibility.AccessibilityNodeProvider; 21 import android.view.inputmethod.EditorInfo; 22 import android.view.inputmethod.InputConnection; 23 import android.widget.FrameLayout; 24 25 import org.chromium.android_webview.AwContents; 26 import org.chromium.android_webview.shell.DrawGL; 27 import org.chromium.content.browser.ContentViewCore; 28 29 import javax.microedition.khronos.egl.EGLConfig; 30 import 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 */ 37 public 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