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.weblayer_private; 6 7 import android.content.Context; 8 import android.graphics.Color; 9 import android.graphics.PixelFormat; 10 import android.graphics.SurfaceTexture; 11 import android.view.Surface; 12 import android.view.SurfaceHolder; 13 import android.view.SurfaceView; 14 import android.view.TextureView; 15 import android.view.View; 16 import android.view.ViewGroup; 17 import android.view.inputmethod.InputMethodManager; 18 import android.webkit.ValueCallback; 19 import android.widget.FrameLayout; 20 21 import androidx.annotation.IntDef; 22 23 import org.chromium.base.annotations.CalledByNative; 24 import org.chromium.base.annotations.JNINamespace; 25 import org.chromium.base.annotations.NativeMethods; 26 import org.chromium.base.task.PostTask; 27 import org.chromium.content_public.browser.UiThreadTaskTraits; 28 import org.chromium.content_public.browser.WebContents; 29 import org.chromium.ui.base.WindowAndroid; 30 import org.chromium.ui.resources.ResourceManager; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.util.ArrayList; 35 36 /** 37 * This class manages the chromium compositor and the Surface that is used by 38 * the chromium compositor. Note it can be used to display only one WebContents. 39 * This allows switching between SurfaceView and TextureView as the source of 40 * the Surface used by chromium compositor, and attempts to make the switch 41 * visually seamless. 42 */ 43 @JNINamespace("weblayer") 44 public class ContentViewRenderView extends FrameLayout { 45 @Retention(RetentionPolicy.SOURCE) 46 @IntDef({MODE_SURFACE_VIEW, MODE_SURFACE_VIEW}) 47 public @interface Mode {} 48 public static final int MODE_SURFACE_VIEW = 0; 49 public static final int MODE_TEXTURE_VIEW = 1; 50 51 // A child view of this class. Parent of SurfaceView/TextureView. 52 // Needed to support not resizing the surface when soft keyboard is showing. 53 private final SurfaceParent mSurfaceParent; 54 55 // This is mode that is requested by client. 56 private SurfaceData mRequested; 57 // This is the mode that last supplied the Surface to the compositor. 58 // This should generally be equal to |mRequested| except during transitions. 59 private SurfaceData mCurrent; 60 61 // The native side of this object. 62 private long mNativeContentViewRenderView; 63 64 private WindowAndroid mWindowAndroid; 65 private WebContents mWebContents; 66 67 private int mBackgroundColor; 68 69 // This is the size of the surfaces, so the "physical" size for the compositor. 70 // This is the size of the |mSurfaceParent| view, which is the immediate parent 71 // of the SurfaceView/TextureView. Note this does not always match the size of 72 // this ContentViewRenderView; when the soft keyboard is displayed, 73 // ContentViewRenderView will shrink in height, but |mSurfaceParent| will not. 74 private int mPhysicalWidth; 75 private int mPhysicalHeight; 76 77 private int mWebContentsHeightDelta; 78 79 // Common interface to listen to surface related events. 80 private interface SurfaceEventListener { surfaceCreated()81 void surfaceCreated(); surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, int width, int height)82 void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, 83 int width, int height); 84 // |cacheBackBuffer| will delay destroying the EGLSurface until after the next swap. surfaceDestroyed(boolean cacheBackBuffer)85 void surfaceDestroyed(boolean cacheBackBuffer); 86 } 87 88 private final ArrayList<TrackedRunnable> mPendingRunnables = new ArrayList<>(); 89 90 // Runnables posted via View.postOnAnimation may not run after the view is detached, 91 // if nothing else causes animation. However a pending runnable may held by a GC root 92 // from the thread itself, and thus can cause leaks. This class here is so ensure that 93 // on destroy, all pending tasks are run immediately so they do not lead to leaks. 94 private abstract class TrackedRunnable implements Runnable { 95 private boolean mHasRun; TrackedRunnable()96 public TrackedRunnable() { 97 mPendingRunnables.add(this); 98 } 99 100 @Override run()101 public final void run() { 102 // View.removeCallbacks is not always reliable, and may run the callback even 103 // after it has been removed. 104 if (mHasRun) return; 105 assert mPendingRunnables.contains(this); 106 mPendingRunnables.remove(this); 107 mHasRun = true; 108 doRun(); 109 } 110 doRun()111 protected abstract void doRun(); 112 } 113 114 // Non-static implementation of SurfaceEventListener that forward calls to native Compositor. 115 // It is also responsible for updating |mRequested| and |mCurrent|. 116 private class SurfaceEventListenerImpl implements SurfaceEventListener { 117 private SurfaceData mSurfaceData; 118 setRequestData(SurfaceData surfaceData)119 public void setRequestData(SurfaceData surfaceData) { 120 assert mSurfaceData == null; 121 mSurfaceData = surfaceData; 122 } 123 124 @Override surfaceCreated()125 public void surfaceCreated() { 126 assert mNativeContentViewRenderView != 0; 127 assert mSurfaceData == ContentViewRenderView.this.mRequested 128 || mSurfaceData == ContentViewRenderView.this.mCurrent; 129 if (ContentViewRenderView.this.mCurrent != null 130 && ContentViewRenderView.this.mCurrent != mSurfaceData) { 131 ContentViewRenderView.this.mCurrent.markForDestroy(true /* hasNextSurface */); 132 mSurfaceData.setSurfaceDataNeedsDestroy(ContentViewRenderView.this.mCurrent); 133 } 134 ContentViewRenderView.this.mCurrent = mSurfaceData; 135 ContentViewRenderViewJni.get().surfaceCreated(mNativeContentViewRenderView); 136 } 137 138 @Override surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, int width, int height)139 public void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, 140 int width, int height) { 141 assert mNativeContentViewRenderView != 0; 142 assert mSurfaceData == ContentViewRenderView.this.mCurrent; 143 ContentViewRenderViewJni.get().surfaceChanged(mNativeContentViewRenderView, 144 canBeUsedWithSurfaceControl, format, width, height, surface); 145 if (mWebContents != null) { 146 ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged( 147 mNativeContentViewRenderView, mWebContents, width, height); 148 } 149 } 150 151 @Override surfaceDestroyed(boolean cacheBackBuffer)152 public void surfaceDestroyed(boolean cacheBackBuffer) { 153 assert mNativeContentViewRenderView != 0; 154 assert mSurfaceData == ContentViewRenderView.this.mCurrent; 155 ContentViewRenderViewJni.get().surfaceDestroyed( 156 mNativeContentViewRenderView, cacheBackBuffer); 157 } 158 } 159 160 // Abstract differences between SurfaceView and TextureView behind this class. 161 // Also responsible for holding and calling callbacks. 162 private class SurfaceData implements SurfaceEventListener { 163 private class TextureViewWithInvalidate extends TextureView { TextureViewWithInvalidate(Context context)164 public TextureViewWithInvalidate(Context context) { 165 super(context); 166 } 167 168 @Override invalidate()169 public void invalidate() { 170 // TextureView is invalidated when it receives a new frame from its SurfaceTexture. 171 // This is a safe place to indicate that this TextureView now has content and is 172 // ready to be shown. 173 super.invalidate(); 174 destroyPreviousData(); 175 } 176 } 177 178 @Mode 179 private final int mMode; 180 private final SurfaceEventListener mListener; 181 private final FrameLayout mParent; 182 private final Runnable mEvict; 183 184 private boolean mRanCallbacks; 185 private boolean mMarkedForDestroy; 186 private boolean mCachedSurfaceNeedsEviction; 187 188 private boolean mNeedsOnSurfaceDestroyed; 189 190 // During transitioning between two SurfaceData, there is a complicated series of calls to 191 // avoid visual artifacts. 192 // 1) Allocate new SurfaceData, and insert it into view hierarchy below the existing 193 // SurfaceData, so it is not yet showing. 194 // 2) When Surface is allocated by new View, swap chromium compositor to the 195 // new Surface. |markForDestroy| is called on the previous SurfaceData, and the two 196 // SurfaceDatas are linked through these two variables. 197 // Note at this point the existing view is still visible. 198 // 3) Wait until new SurfaceData decides that it has content and is ready to be shown 199 // * For TextureView, wait until TextureView.invalidate is called 200 // * For SurfaceView, wait for two swaps from the chromium compositor 201 // 4) New SurfaceData calls |destroy| on previous SurfaceData. 202 // * For TextureView, it is simply detached immediately from the view tree 203 // * For SurfaceView, to avoid flicker, move it to the back first before and wait 204 // two frames before detaching. 205 // 5) Previous SurfaceData runs callbacks on the new SurfaceData to signal the completion 206 // of the transition. 207 private SurfaceData mPrevSurfaceDataNeedsDestroy; 208 private SurfaceData mNextSurfaceDataNeedsRunCallback; 209 210 private final SurfaceHolderCallback mSurfaceCallback; 211 private final SurfaceView mSurfaceView; 212 private int mNumSurfaceViewSwapsUntilVisible; 213 214 private final TextureView mTextureView; 215 private final TextureViewSurfaceTextureListener mSurfaceTextureListener; 216 217 private final ArrayList<ValueCallback<Boolean>> mModeCallbacks = new ArrayList<>(); 218 SurfaceData(@ode int mode, FrameLayout parent, SurfaceEventListener listener, int backgroundColor, Runnable evict)219 public SurfaceData(@Mode int mode, FrameLayout parent, SurfaceEventListener listener, 220 int backgroundColor, Runnable evict) { 221 mMode = mode; 222 mListener = listener; 223 mParent = parent; 224 mEvict = evict; 225 if (mode == MODE_SURFACE_VIEW) { 226 mSurfaceView = new SurfaceView(parent.getContext()); 227 mSurfaceView.setZOrderMediaOverlay(true); 228 mSurfaceView.setBackgroundColor(backgroundColor); 229 230 mSurfaceCallback = new SurfaceHolderCallback(this); 231 mSurfaceView.getHolder().addCallback(mSurfaceCallback); 232 mSurfaceView.setVisibility(View.VISIBLE); 233 234 // TODO(boliu): This is only needed when video is lifted into a separate surface. 235 // Keeping this constantly will use one more byte per pixel constantly. 236 mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); 237 238 mTextureView = null; 239 mSurfaceTextureListener = null; 240 } else if (mode == MODE_TEXTURE_VIEW) { 241 mTextureView = new TextureViewWithInvalidate(parent.getContext()); 242 mSurfaceTextureListener = new TextureViewSurfaceTextureListener(this); 243 mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 244 mTextureView.setVisibility(VISIBLE); 245 246 mSurfaceView = null; 247 mSurfaceCallback = null; 248 } else { 249 throw new RuntimeException("Illegal mode: " + mode); 250 } 251 252 // This postOnAnimation is to avoid manipulating the view tree inside layout or draw. 253 parent.postOnAnimation(new TrackedRunnable() { 254 @Override 255 protected void doRun() { 256 if (mMarkedForDestroy) return; 257 View view = (mMode == MODE_SURFACE_VIEW) ? mSurfaceView : mTextureView; 258 assert view != null; 259 // Always insert view for new surface below the existing view to avoid artifacts 260 // during surface swaps. Index 0 is the lowest child. 261 mParent.addView(view, 0, 262 new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 263 FrameLayout.LayoutParams.MATCH_PARENT)); 264 mParent.invalidate(); 265 } 266 }); 267 } 268 setSurfaceDataNeedsDestroy(SurfaceData surfaceData)269 public void setSurfaceDataNeedsDestroy(SurfaceData surfaceData) { 270 assert !mMarkedForDestroy; 271 assert mPrevSurfaceDataNeedsDestroy == null; 272 mPrevSurfaceDataNeedsDestroy = surfaceData; 273 mPrevSurfaceDataNeedsDestroy.mNextSurfaceDataNeedsRunCallback = this; 274 } 275 getMode()276 public @Mode int getMode() { 277 return mMode; 278 } 279 addCallback(ValueCallback<Boolean> callback)280 public void addCallback(ValueCallback<Boolean> callback) { 281 assert !mMarkedForDestroy; 282 mModeCallbacks.add(callback); 283 if (mRanCallbacks) runCallbacks(); 284 } 285 286 // Tearing down is separated into markForDestroy and destroy. After markForDestroy 287 // this class will is guaranteed to not issue any calls to its SurfaceEventListener. markForDestroy(boolean hasNextSurface)288 public void markForDestroy(boolean hasNextSurface) { 289 if (mMarkedForDestroy) return; 290 mMarkedForDestroy = true; 291 292 if (mNeedsOnSurfaceDestroyed) { 293 // SurfaceView being used with SurfaceControl need to cache the back buffer 294 // (EGLSurface). Otherwise the surface is destroyed immediate before the 295 // SurfaceView is detached. 296 mCachedSurfaceNeedsEviction = hasNextSurface && mMode == MODE_SURFACE_VIEW; 297 mListener.surfaceDestroyed(mCachedSurfaceNeedsEviction); 298 mNeedsOnSurfaceDestroyed = false; 299 } 300 301 if (mMode == MODE_SURFACE_VIEW) { 302 mSurfaceView.getHolder().removeCallback(mSurfaceCallback); 303 } else if (mMode == MODE_TEXTURE_VIEW) { 304 mTextureView.setSurfaceTextureListener(null); 305 } else { 306 assert false; 307 } 308 } 309 310 // Remove view from parent hierarchy. destroy()311 public void destroy() { 312 assert mMarkedForDestroy; 313 runCallbacks(); 314 // This postOnAnimation is to avoid manipulating the view tree inside layout or draw. 315 mParent.postOnAnimation(new TrackedRunnable() { 316 @Override 317 protected void doRun() { 318 if (mMode == MODE_SURFACE_VIEW) { 319 // Detaching a SurfaceView causes a flicker because the SurfaceView tears 320 // down the Surface in SurfaceFlinger before removing its hole in the view 321 // tree. This is a complicated heuristics to avoid this. It first moves the 322 // SurfaceView behind the new View. Then wait two frames before detaching 323 // the SurfaceView. Waiting for a single frame still causes flickers on 324 // high end devices like Pixel 3. 325 moveChildToBackWithoutDetach(mParent, mSurfaceView); 326 TrackedRunnable inner = new TrackedRunnable() { 327 @Override 328 public void doRun() { 329 mParent.removeView(mSurfaceView); 330 mParent.invalidate(); 331 if (mCachedSurfaceNeedsEviction) { 332 mEvict.run(); 333 mCachedSurfaceNeedsEviction = false; 334 } 335 runCallbackOnNextSurfaceData(); 336 } 337 }; 338 TrackedRunnable outer = new TrackedRunnable() { 339 @Override 340 public void doRun() { 341 mParent.postOnAnimation(inner); 342 } 343 }; 344 mParent.postOnAnimation(outer); 345 } else if (mMode == MODE_TEXTURE_VIEW) { 346 mParent.removeView(mTextureView); 347 runCallbackOnNextSurfaceData(); 348 } else { 349 assert false; 350 } 351 } 352 }); 353 } 354 moveChildToBackWithoutDetach(ViewGroup parent, View child)355 private void moveChildToBackWithoutDetach(ViewGroup parent, View child) { 356 final int numberOfChildren = parent.getChildCount(); 357 final int childIndex = parent.indexOfChild(child); 358 if (childIndex <= 0) return; 359 for (int i = 0; i < childIndex; ++i) { 360 parent.bringChildToFront(parent.getChildAt(0)); 361 } 362 assert parent.indexOfChild(child) == 0; 363 for (int i = 0; i < numberOfChildren - childIndex - 1; ++i) { 364 parent.bringChildToFront(parent.getChildAt(1)); 365 } 366 parent.invalidate(); 367 } 368 setBackgroundColor(int color)369 public void setBackgroundColor(int color) { 370 assert !mMarkedForDestroy; 371 if (mMode == MODE_SURFACE_VIEW) { 372 mSurfaceView.setBackgroundColor(color); 373 } 374 } 375 376 /** @return true if should keep swapping frames */ didSwapFrame()377 public boolean didSwapFrame() { 378 if (mSurfaceView != null && mSurfaceView.getBackground() != null) { 379 mSurfaceView.post(new Runnable() { 380 @Override 381 public void run() { 382 if (mSurfaceView != null) mSurfaceView.setBackgroundResource(0); 383 } 384 }); 385 } 386 if (mMode == MODE_SURFACE_VIEW) { 387 // We have no reliable signal for when to show a SurfaceView. This is a heuristic 388 // (used by chrome as well) is to wait for 2 swaps from the chromium comopsitor 389 // as a signal that the SurfaceView has content and is ready to be displayed. 390 if (mNumSurfaceViewSwapsUntilVisible > 0) { 391 mNumSurfaceViewSwapsUntilVisible--; 392 } 393 if (mNumSurfaceViewSwapsUntilVisible == 0) { 394 destroyPreviousData(); 395 } 396 return mNumSurfaceViewSwapsUntilVisible > 0; 397 } 398 return false; 399 } 400 destroyPreviousData()401 private void destroyPreviousData() { 402 if (mPrevSurfaceDataNeedsDestroy != null) { 403 mPrevSurfaceDataNeedsDestroy.destroy(); 404 mPrevSurfaceDataNeedsDestroy = null; 405 } 406 } 407 408 @Override surfaceCreated()409 public void surfaceCreated() { 410 if (mMarkedForDestroy) return; 411 412 // On pre-M Android, layers start in the hidden state until a relayout happens. 413 // There is a bug that manifests itself when entering overlay mode on pre-M devices, 414 // where a relayout never happens. This bug is out of Chromium's control, but can be 415 // worked around by forcibly re-setting the visibility of the surface view. 416 // Otherwise, the screen stays black, and some tests fail. 417 if (mSurfaceView != null) { 418 mSurfaceView.setVisibility(mSurfaceView.getVisibility()); 419 } 420 mListener.surfaceCreated(); 421 422 if (!mRanCallbacks && mPrevSurfaceDataNeedsDestroy == null) { 423 runCallbacks(); 424 } 425 426 mNeedsOnSurfaceDestroyed = true; 427 } 428 429 @Override surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, int width, int height)430 public void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, 431 int width, int height) { 432 if (mMarkedForDestroy) return; 433 mListener.surfaceChanged(surface, canBeUsedWithSurfaceControl, format, width, height); 434 mNumSurfaceViewSwapsUntilVisible = 2; 435 } 436 437 @Override surfaceDestroyed(boolean cacheBackBuffer)438 public void surfaceDestroyed(boolean cacheBackBuffer) { 439 if (mMarkedForDestroy) return; 440 assert mNeedsOnSurfaceDestroyed; 441 mListener.surfaceDestroyed(cacheBackBuffer); 442 mNeedsOnSurfaceDestroyed = false; 443 } 444 runCallbacks()445 private void runCallbacks() { 446 mRanCallbacks = true; 447 if (mModeCallbacks.isEmpty()) return; 448 // PostTask to avoid possible reentrancy problems with embedder code. 449 PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { 450 ArrayList<ValueCallback<Boolean>> clone = 451 (ArrayList<ValueCallback<Boolean>>) mModeCallbacks.clone(); 452 mModeCallbacks.clear(); 453 for (ValueCallback<Boolean> run : clone) { 454 run.onReceiveValue(!mMarkedForDestroy); 455 } 456 }); 457 } 458 runCallbackOnNextSurfaceData()459 private void runCallbackOnNextSurfaceData() { 460 if (mNextSurfaceDataNeedsRunCallback != null) { 461 mNextSurfaceDataNeedsRunCallback.runCallbacks(); 462 mNextSurfaceDataNeedsRunCallback = null; 463 } 464 } 465 } 466 467 // Adapter for SurfaceHoolder.Callback. 468 private static class SurfaceHolderCallback implements SurfaceHolder.Callback { 469 private final SurfaceEventListener mListener; 470 SurfaceHolderCallback(SurfaceEventListener listener)471 public SurfaceHolderCallback(SurfaceEventListener listener) { 472 mListener = listener; 473 } 474 475 @Override surfaceCreated(SurfaceHolder holder)476 public void surfaceCreated(SurfaceHolder holder) { 477 mListener.surfaceCreated(); 478 } 479 480 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)481 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 482 mListener.surfaceChanged(holder.getSurface(), true, format, width, height); 483 } 484 485 @Override surfaceDestroyed(SurfaceHolder holder)486 public void surfaceDestroyed(SurfaceHolder holder) { 487 mListener.surfaceDestroyed(false /* cacheBackBuffer */); 488 } 489 } 490 491 // Adapter for TextureView.SurfaceTextureListener. 492 private static class TextureViewSurfaceTextureListener 493 implements TextureView.SurfaceTextureListener { 494 private final SurfaceEventListener mListener; 495 496 private SurfaceTexture mCurrentSurfaceTexture; 497 private Surface mCurrentSurface; 498 TextureViewSurfaceTextureListener(SurfaceEventListener listener)499 public TextureViewSurfaceTextureListener(SurfaceEventListener listener) { 500 mListener = listener; 501 } 502 503 @Override onSurfaceTextureAvailable( SurfaceTexture surfaceTexture, int width, int height)504 public void onSurfaceTextureAvailable( 505 SurfaceTexture surfaceTexture, int width, int height) { 506 mListener.surfaceCreated(); 507 onSurfaceTextureSizeChanged(surfaceTexture, width, height); 508 } 509 510 @Override onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)511 public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 512 mListener.surfaceDestroyed(false /* cacheBackBuffer */); 513 return true; 514 } 515 516 @Override onSurfaceTextureSizeChanged( SurfaceTexture surfaceTexture, int width, int height)517 public void onSurfaceTextureSizeChanged( 518 SurfaceTexture surfaceTexture, int width, int height) { 519 if (mCurrentSurfaceTexture != surfaceTexture) { 520 mCurrentSurfaceTexture = surfaceTexture; 521 mCurrentSurface = new Surface(mCurrentSurfaceTexture); 522 } 523 mListener.surfaceChanged(mCurrentSurface, false, PixelFormat.OPAQUE, width, height); 524 } 525 526 @Override onSurfaceTextureUpdated(SurfaceTexture surfaceTexture)527 public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {} 528 } 529 530 // This is a child of ContentViewRenderView and parent of SurfaceView/TextureView. 531 // This exists to avoid resizing SurfaceView/TextureView when the soft keyboard is displayed. 532 private class SurfaceParent extends FrameLayout { SurfaceParent(Context context)533 public SurfaceParent(Context context) { 534 super(context); 535 } 536 537 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)538 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 539 int existingHeight = getMeasuredHeight(); 540 // If width is the same and height shrinks, then check if we should 541 // avoid this resize for displaying the soft keyboard. 542 if (getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) 543 && existingHeight > MeasureSpec.getSize(heightMeasureSpec) 544 && shouldAvoidSurfaceResizeForSoftKeyboard()) { 545 // Just set the height to the current height. 546 heightMeasureSpec = 547 MeasureSpec.makeMeasureSpec(existingHeight, MeasureSpec.EXACTLY); 548 } 549 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 550 } 551 552 @Override onSizeChanged(int w, int h, int oldw, int oldh)553 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 554 mPhysicalWidth = w; 555 mPhysicalHeight = h; 556 } 557 } 558 559 /** 560 * Constructs a new ContentViewRenderView. 561 * This should be called and the {@link ContentViewRenderView} should be added to the view 562 * hierarchy before the first draw to avoid a black flash that is seen every time a 563 * {@link SurfaceView} is added. 564 * @param context The context used to create this. 565 */ ContentViewRenderView(Context context)566 public ContentViewRenderView(Context context) { 567 super(context); 568 mSurfaceParent = new SurfaceParent(context); 569 addView(mSurfaceParent, 570 new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 571 setBackgroundColor(Color.WHITE); 572 } 573 574 /** 575 * Initialization that requires native libraries should be done here. 576 * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer. 577 * @param rootWindow The {@link WindowAndroid} this render view should be linked to. 578 */ onNativeLibraryLoaded(WindowAndroid rootWindow, @Mode int mode)579 public void onNativeLibraryLoaded(WindowAndroid rootWindow, @Mode int mode) { 580 assert rootWindow != null; 581 mNativeContentViewRenderView = 582 ContentViewRenderViewJni.get().init(ContentViewRenderView.this, rootWindow); 583 assert mNativeContentViewRenderView != 0; 584 mWindowAndroid = rootWindow; 585 requestMode(mode, (Boolean result) -> {}); 586 } 587 requestMode(@ode int mode, ValueCallback<Boolean> callback)588 public void requestMode(@Mode int mode, ValueCallback<Boolean> callback) { 589 assert mode == MODE_SURFACE_VIEW || mode == MODE_TEXTURE_VIEW; 590 assert callback != null; 591 if (mRequested != null && mRequested.getMode() != mode) { 592 if (mRequested != mCurrent) { 593 mRequested.markForDestroy(false /* hasNextSurface */); 594 mRequested.destroy(); 595 } 596 mRequested = null; 597 } 598 599 if (mRequested == null) { 600 SurfaceEventListenerImpl listener = new SurfaceEventListenerImpl(); 601 mRequested = new SurfaceData( 602 mode, mSurfaceParent, listener, mBackgroundColor, this::evictCachedSurface); 603 listener.setRequestData(mRequested); 604 } 605 assert mRequested.getMode() == mode; 606 mRequested.addCallback(callback); 607 } 608 609 /** 610 * Sets how much to decrease the height of the WebContents by. 611 */ setWebContentsHeightDelta(int delta)612 public void setWebContentsHeightDelta(int delta) { 613 if (delta == mWebContentsHeightDelta) return; 614 mWebContentsHeightDelta = delta; 615 updateWebContentsSize(); 616 } 617 updateWebContentsSize()618 private void updateWebContentsSize() { 619 if (mWebContents == null) return; 620 mWebContents.setSize(getWidth(), getHeight() - mWebContentsHeightDelta); 621 } 622 623 @Override onSizeChanged(int w, int h, int oldw, int oldh)624 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 625 updateWebContentsSize(); 626 } 627 628 /** 629 * View's method override to notify WindowAndroid about changes in its visibility. 630 */ 631 @Override onWindowVisibilityChanged(int visibility)632 protected void onWindowVisibilityChanged(int visibility) { 633 super.onWindowVisibilityChanged(visibility); 634 635 if (mWindowAndroid == null) return; 636 637 if (visibility == View.GONE) { 638 mWindowAndroid.onVisibilityChanged(false); 639 } else if (visibility == View.VISIBLE) { 640 mWindowAndroid.onVisibilityChanged(true); 641 } 642 } 643 644 /** 645 * Sets the background color of the surface / texture view. This method is necessary because 646 * the background color of ContentViewRenderView itself is covered by the background of 647 * SurfaceView. 648 * @param color The color of the background. 649 */ 650 @Override setBackgroundColor(int color)651 public void setBackgroundColor(int color) { 652 super.setBackgroundColor(color); 653 mBackgroundColor = color; 654 if (mRequested != null) { 655 mRequested.setBackgroundColor(color); 656 } 657 if (mCurrent != null) { 658 mCurrent.setBackgroundColor(color); 659 } 660 } 661 662 /** 663 * Should be called when the ContentViewRenderView is not needed anymore so its associated 664 * native resource can be freed. 665 */ destroy()666 public void destroy() { 667 if (mRequested != null) { 668 mRequested.markForDestroy(false /* hasNextSurface */); 669 mRequested.destroy(); 670 if (mCurrent != null && mCurrent != mRequested) { 671 mCurrent.markForDestroy(false /* hasNextSurface */); 672 mCurrent.destroy(); 673 } 674 } 675 mRequested = null; 676 mCurrent = null; 677 678 mWindowAndroid = null; 679 680 while (!mPendingRunnables.isEmpty()) { 681 TrackedRunnable runnable = mPendingRunnables.get(0); 682 removeCallbacks(runnable); 683 runnable.run(); 684 assert !mPendingRunnables.contains(runnable); 685 } 686 ContentViewRenderViewJni.get().destroy(mNativeContentViewRenderView); 687 mNativeContentViewRenderView = 0; 688 } 689 setWebContents(WebContents webContents)690 public void setWebContents(WebContents webContents) { 691 assert mNativeContentViewRenderView != 0; 692 mWebContents = webContents; 693 694 if (webContents != null) { 695 updateWebContentsSize(); 696 ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged( 697 mNativeContentViewRenderView, webContents, mPhysicalWidth, mPhysicalHeight); 698 } 699 ContentViewRenderViewJni.get().setCurrentWebContents( 700 mNativeContentViewRenderView, webContents); 701 } 702 getResourceManager()703 public ResourceManager getResourceManager() { 704 return ContentViewRenderViewJni.get().getResourceManager(mNativeContentViewRenderView); 705 } 706 707 @CalledByNative didSwapFrame()708 private boolean didSwapFrame() { 709 assert mCurrent != null; 710 return mCurrent.didSwapFrame(); 711 } 712 evictCachedSurface()713 private void evictCachedSurface() { 714 if (mNativeContentViewRenderView == 0) return; 715 ContentViewRenderViewJni.get().evictCachedSurface(mNativeContentViewRenderView); 716 } 717 getNativeHandle()718 public long getNativeHandle() { 719 return mNativeContentViewRenderView; 720 } 721 shouldAvoidSurfaceResizeForSoftKeyboard()722 private boolean shouldAvoidSurfaceResizeForSoftKeyboard() { 723 // TextureView is more common with embedding use cases that should lead to resize. 724 boolean usingSurfaceView = mCurrent != null && mCurrent.getMode() == MODE_SURFACE_VIEW; 725 if (!usingSurfaceView) return false; 726 727 boolean isFullWidth = isAttachedToWindow() && getWidth() == getRootView().getWidth(); 728 if (!isFullWidth) return false; 729 730 InputMethodManager inputMethodManager = 731 (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 732 return inputMethodManager.isActive(); 733 } 734 735 @NativeMethods 736 interface Natives { init(ContentViewRenderView caller, WindowAndroid rootWindow)737 long init(ContentViewRenderView caller, WindowAndroid rootWindow); destroy(long nativeContentViewRenderView)738 void destroy(long nativeContentViewRenderView); setCurrentWebContents(long nativeContentViewRenderView, WebContents webContents)739 void setCurrentWebContents(long nativeContentViewRenderView, WebContents webContents); onPhysicalBackingSizeChanged( long nativeContentViewRenderView, WebContents webContents, int width, int height)740 void onPhysicalBackingSizeChanged( 741 long nativeContentViewRenderView, WebContents webContents, int width, int height); surfaceCreated(long nativeContentViewRenderView)742 void surfaceCreated(long nativeContentViewRenderView); surfaceDestroyed(long nativeContentViewRenderView, boolean cacheBackBuffer)743 void surfaceDestroyed(long nativeContentViewRenderView, boolean cacheBackBuffer); surfaceChanged(long nativeContentViewRenderView, boolean canBeUsedWithSurfaceControl, int format, int width, int height, Surface surface)744 void surfaceChanged(long nativeContentViewRenderView, boolean canBeUsedWithSurfaceControl, 745 int format, int width, int height, Surface surface); evictCachedSurface(long nativeContentViewRenderView)746 void evictCachedSurface(long nativeContentViewRenderView); getResourceManager(long nativeContentViewRenderView)747 ResourceManager getResourceManager(long nativeContentViewRenderView); 748 } 749 } 750