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