| Index: chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
|
| diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2d4e56c47ccb0a8db09632b6cb6c1f46b34f369e
|
| --- /dev/null
|
| +++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
|
| @@ -0,0 +1,1105 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.chrome.browser.compositor;
|
| +
|
| +import android.content.Context;
|
| +import android.graphics.Canvas;
|
| +import android.graphics.Paint;
|
| +import android.graphics.Rect;
|
| +import android.graphics.RectF;
|
| +import android.os.Bundle;
|
| +import android.os.Handler;
|
| +import android.support.v4.view.ViewCompat;
|
| +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
| +import android.support.v4.widget.ExploreByTouchHelper;
|
| +import android.util.AttributeSet;
|
| +import android.util.Pair;
|
| +import android.view.MotionEvent;
|
| +import android.view.SurfaceHolder;
|
| +import android.view.SurfaceView;
|
| +import android.view.View;
|
| +import android.view.ViewGroup;
|
| +import android.view.accessibility.AccessibilityEvent;
|
| +import android.view.inputmethod.InputMethodManager;
|
| +import android.widget.FrameLayout;
|
| +
|
| +import com.google.android.apps.chrome.R;
|
| +
|
| +import org.chromium.base.SysUtils;
|
| +import org.chromium.base.TraceEvent;
|
| +import org.chromium.base.annotations.SuppressFBWarnings;
|
| +import org.chromium.chrome.browser.EmptyTabObserver;
|
| +import org.chromium.chrome.browser.Tab;
|
| +import org.chromium.chrome.browser.TabObserver;
|
| +import org.chromium.chrome.browser.compositor.Invalidator.Client;
|
| +import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
|
| +import org.chromium.chrome.browser.compositor.layouts.LayoutManagerHost;
|
| +import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
|
| +import org.chromium.chrome.browser.compositor.layouts.components.VirtualView;
|
| +import org.chromium.chrome.browser.compositor.layouts.content.ContentOffsetProvider;
|
| +import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
|
| +import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDelegate;
|
| +import org.chromium.chrome.browser.device.DeviceClassManager;
|
| +import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
|
| +import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener;
|
| +import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
|
| +import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
|
| +import org.chromium.chrome.browser.tabmodel.TabModelSelector;
|
| +import org.chromium.chrome.browser.widget.ControlContainer;
|
| +import org.chromium.content.browser.ContentReadbackHandler;
|
| +import org.chromium.content.browser.ContentViewCore;
|
| +import org.chromium.content.browser.SPenSupport;
|
| +import org.chromium.ui.UiUtils;
|
| +import org.chromium.ui.base.DeviceFormFactor;
|
| +import org.chromium.ui.base.WindowAndroid;
|
| +import org.chromium.ui.resources.ResourceManager;
|
| +import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
|
| +import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
|
| +
|
| +import java.util.ArrayList;
|
| +import java.util.List;
|
| +
|
| +/**
|
| + * This class holds a {@link CompositorView}. This level of indirection is needed to benefit from
|
| + * the {@link android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)} capability on
|
| + * available on {@link android.view.ViewGroup}s.
|
| + * This class also holds the {@link LayoutManager} responsible to describe the items to be
|
| + * drawn by the UI compositor on the native side.
|
| + */
|
| +public class CompositorViewHolder extends FrameLayout
|
| + implements LayoutManagerHost, LayoutRenderHost, Invalidator.Host, FullscreenListener {
|
| + private static List<View> sCachedViewList = new ArrayList<View>();
|
| + private static List<ContentViewCore> sCachedCVCList = new ArrayList<ContentViewCore>();
|
| +
|
| + private boolean mIsKeyboardShowing = false;
|
| +
|
| + private final Invalidator mInvalidator = new Invalidator();
|
| + private LayoutManager mLayoutManager;
|
| + private LayerTitleCache mLayerTitleCache;
|
| + private CompositorView mCompositorView;
|
| +
|
| + private boolean mContentOverlayVisiblity = true;
|
| +
|
| + private int mPendingSwapBuffersCount;
|
| +
|
| + private final ArrayList<Invalidator.Client> mPendingInvalidations =
|
| + new ArrayList<Invalidator.Client>();
|
| + private boolean mSkipInvalidation = false;
|
| +
|
| + private boolean mSkipNextToolbarTextureUpdate = false;
|
| +
|
| + /**
|
| + * A task to be performed after a resize event.
|
| + */
|
| + private Runnable mPostHideKeyboardTask;
|
| +
|
| + private TabModelSelector mTabModelSelector;
|
| + private ChromeFullscreenManager mFullscreenManager;
|
| + private View mAccessibilityView;
|
| + private CompositorAccessibilityProvider mNodeProvider;
|
| + private boolean mFullscreenTouchEvent = false;
|
| + private float mLastContentOffset = 0;
|
| + private float mLastVisibleContentOffset = 0;
|
| +
|
| + /** The toolbar control container. **/
|
| + private ControlContainer mControlContainer;
|
| +
|
| + /** The currently visible Tab. */
|
| + private Tab mTabVisible;
|
| +
|
| + /** The currently attached View. */
|
| + private View mView;
|
| +
|
| + private TabObserver mTabObserver;
|
| + private boolean mEnableCompositorTabStrip;
|
| +
|
| + // Cache objects that should not be created frequently.
|
| + private final Rect mCacheViewport = new Rect();
|
| + private final Rect mCacheVisibleViewport = new Rect();
|
| +
|
| + // If we've drawn at least one frame.
|
| + private boolean mHasDrawnOnce = false;
|
| +
|
| + /**
|
| + * This view is created on demand to display debugging information.
|
| + */
|
| + private static class DebugOverlay extends View {
|
| + private final List<Pair<Rect, Integer>> mRectangles = new ArrayList<Pair<Rect, Integer>>();
|
| + private final Paint mPaint = new Paint();
|
| + private boolean mFirstPush = true;
|
| +
|
| + /**
|
| + * @param context The current Android's context.
|
| + */
|
| + public DebugOverlay(Context context) {
|
| + super(context);
|
| + }
|
| +
|
| + /**
|
| + * Pushes a rectangle to be drawn on the screen on top of everything.
|
| + *
|
| + * @param rect The rectangle to be drawn on screen
|
| + * @param color The color of the rectangle
|
| + */
|
| + public void pushRect(Rect rect, int color) {
|
| + if (mFirstPush) {
|
| + mRectangles.clear();
|
| + mFirstPush = false;
|
| + }
|
| + mRectangles.add(new Pair<Rect, Integer>(rect, color));
|
| + invalidate();
|
| + }
|
| +
|
| + @SuppressFBWarnings("NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD")
|
| + @Override
|
| + protected void onDraw(Canvas canvas) {
|
| + for (int i = 0; i < mRectangles.size(); i++) {
|
| + mPaint.setColor(mRectangles.get(i).second);
|
| + canvas.drawRect(mRectangles.get(i).first, mPaint);
|
| + }
|
| + mFirstPush = true;
|
| + }
|
| + }
|
| +
|
| + private DebugOverlay mDebugOverlay;
|
| +
|
| + private View mUrlBar;
|
| +
|
| + /**
|
| + * Creates a {@link CompositorView}.
|
| + * @param c The Context to create this {@link CompositorView} in.
|
| + */
|
| + public CompositorViewHolder(Context c) {
|
| + super(c);
|
| +
|
| + internalInit();
|
| + }
|
| +
|
| + /**
|
| + * Creates a {@link CompositorView}.
|
| + * @param c The Context to create this {@link CompositorView} in.
|
| + * @param attrs The AttributeSet used to create this {@link CompositorView}.
|
| + */
|
| + public CompositorViewHolder(Context c, AttributeSet attrs) {
|
| + super(c, attrs);
|
| +
|
| + internalInit();
|
| + }
|
| +
|
| + private void internalInit() {
|
| + mTabObserver = new EmptyTabObserver() {
|
| + @Override
|
| + public void onContentChanged(Tab tab) {
|
| + CompositorViewHolder.this.onContentChanged();
|
| + }
|
| +
|
| + @Override
|
| + public void onOverlayContentViewCoreAdded(Tab tab, ContentViewCore content) {
|
| + initializeContentViewCore(content);
|
| + setSizeOfUnattachedView(content.getContainerView());
|
| + }
|
| + };
|
| +
|
| + mEnableCompositorTabStrip = DeviceFormFactor.isTablet(getContext());
|
| +
|
| + addOnLayoutChangeListener(new OnLayoutChangeListener() {
|
| + @Override
|
| + public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
| + int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
| + propagateViewportToLayouts(right - left, bottom - top);
|
| +
|
| + // If there's an event that needs to occur after the keyboard is hidden, post
|
| + // it as a delayed event. Otherwise this happens in the midst of the
|
| + // ContentView's relayout, which causes the ContentView to relayout on top of the
|
| + // stack view. The 30ms is arbitrary, hoping to let the view get one repaint
|
| + // in so the full page is shown.
|
| + if (mPostHideKeyboardTask != null) {
|
| + new Handler().postDelayed(mPostHideKeyboardTask, 30);
|
| + mPostHideKeyboardTask = null;
|
| + }
|
| + }
|
| + });
|
| +
|
| + mCompositorView = new CompositorView(getContext(), this);
|
| + addView(mCompositorView,
|
| + new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
| + }
|
| +
|
| + /**
|
| + * @param layoutManager The {@link LayoutManager} instance that will be driving what
|
| + * shows in this {@link CompositorViewHolder}.
|
| + */
|
| + public void setLayoutManager(LayoutManager layoutManager) {
|
| + mLayoutManager = layoutManager;
|
| + propagateViewportToLayouts(getWidth(), getHeight());
|
| + }
|
| +
|
| + /**
|
| + * @param view The root view of the hierarchy.
|
| + */
|
| + public void setRootView(View view) {
|
| + mCompositorView.setRootView(view);
|
| + }
|
| +
|
| + /**
|
| + * @param controlContainer The ControlContainer.
|
| + */
|
| + public void setControlContainer(ControlContainer controlContainer) {
|
| + DynamicResourceLoader loader = mCompositorView.getResourceManager() != null
|
| + ? mCompositorView.getResourceManager().getDynamicResourceLoader()
|
| + : null;
|
| + if (loader != null && mControlContainer != null) {
|
| + loader.unregisterResource(R.id.control_container);
|
| + loader.unregisterResource(R.id.progress);
|
| + }
|
| + mControlContainer = controlContainer;
|
| + if (loader != null && mControlContainer != null) {
|
| + loader.registerResource(
|
| + R.id.control_container, mControlContainer.getToolbarResourceAdapter());
|
| +
|
| + ViewResourceAdapter progressAdapter = mControlContainer.getProgressResourceAdapter();
|
| + if (progressAdapter != null) {
|
| + loader.registerResource(R.id.progress, progressAdapter);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @return The CompositorView.
|
| + */
|
| + public SurfaceHolder.Callback2 getSurfaceHolderCallback2() {
|
| + return mCompositorView;
|
| + }
|
| +
|
| + /**
|
| + * Reset command line flags. This gets called after the native library finishes
|
| + * loading.
|
| + */
|
| + public void resetFlags() {
|
| + mCompositorView.resetFlags();
|
| + }
|
| +
|
| + /**
|
| + * Should be called for cleanup when the CompositorView instance is no longer used.
|
| + */
|
| + public void shutDown() {
|
| + setTab(null);
|
| + if (mLayerTitleCache != null) mLayerTitleCache.shutDown();
|
| + mCompositorView.shutDown();
|
| + }
|
| +
|
| + /**
|
| + * This is called when the native library are ready.
|
| + */
|
| + public void onNativeLibraryReady(
|
| + WindowAndroid windowAndroid, TabContentManager tabContentManager) {
|
| + assert mLayerTitleCache == null : "Should be called once";
|
| +
|
| + if (DeviceClassManager.enableLayerDecorationCache()) {
|
| + mLayerTitleCache = new LayerTitleCache(getContext());
|
| + }
|
| +
|
| + mCompositorView.initNativeCompositor(
|
| + SysUtils.isLowEndDevice(), windowAndroid, mLayerTitleCache, tabContentManager);
|
| +
|
| + if (mLayerTitleCache != null) {
|
| + mLayerTitleCache.setResourceManager(getResourceManager());
|
| + }
|
| +
|
| + if (mControlContainer != null) {
|
| + mCompositorView.getResourceManager().getDynamicResourceLoader().registerResource(
|
| + R.id.control_container, mControlContainer.getToolbarResourceAdapter());
|
| +
|
| + ViewResourceAdapter progressAdapter = mControlContainer.getProgressResourceAdapter();
|
| + if (progressAdapter != null) {
|
| + mCompositorView.getResourceManager().getDynamicResourceLoader().registerResource(
|
| + R.id.progress, progressAdapter);
|
| + }
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public ResourceManager getResourceManager() {
|
| + return mCompositorView.getResourceManager();
|
| + }
|
| +
|
| + public ContentOffsetProvider getContentOffsetProvider() {
|
| + return mCompositorView;
|
| + }
|
| +
|
| + /**
|
| + * @return The content readback handler.
|
| + */
|
| + public ContentReadbackHandler getContentReadbackHandler() {
|
| + if (mCompositorView == null) return null;
|
| + return mCompositorView.getContentReadbackHandler();
|
| + }
|
| +
|
| + /**
|
| + * @return The {@link DynamicResourceLoader} for registering resources.
|
| + */
|
| + public DynamicResourceLoader getDynamicResourceLoader() {
|
| + return mCompositorView.getResourceManager().getDynamicResourceLoader();
|
| + }
|
| +
|
| + /**
|
| + * @return The {@link Invalidator} instance that is driven by this {@link CompositorViewHolder}.
|
| + */
|
| + public Invalidator getInvalidator() {
|
| + return mInvalidator;
|
| + }
|
| +
|
| + @Override
|
| + public boolean onInterceptTouchEvent(MotionEvent e) {
|
| + super.onInterceptTouchEvent(e);
|
| +
|
| + if (mLayoutManager == null) return false;
|
| +
|
| + mFullscreenTouchEvent = false;
|
| + if (mFullscreenManager != null && mFullscreenManager.onInterceptMotionEvent(e)
|
| + && !mEnableCompositorTabStrip) {
|
| + // Don't eat the event if the new tab strip is enabled.
|
| + mFullscreenTouchEvent = true;
|
| + return true;
|
| + }
|
| +
|
| + setContentViewMotionEventOffsets(e, false);
|
| + return mLayoutManager.onInterceptTouchEvent(e, mIsKeyboardShowing);
|
| + }
|
| +
|
| + @Override
|
| + public boolean onTouchEvent(MotionEvent e) {
|
| + if (mFullscreenManager != null) mFullscreenManager.onMotionEvent(e);
|
| + if (mFullscreenTouchEvent) return true;
|
| + boolean consumed = mLayoutManager != null && mLayoutManager.onTouchEvent(e);
|
| + setContentViewMotionEventOffsets(e, true);
|
| + return consumed;
|
| + }
|
| +
|
| + @Override
|
| + public boolean onInterceptHoverEvent(MotionEvent e) {
|
| + setContentViewMotionEventOffsets(e, true);
|
| + return super.onInterceptHoverEvent(e);
|
| + }
|
| +
|
| + @Override
|
| + public boolean dispatchHoverEvent(MotionEvent e) {
|
| + if (mNodeProvider != null) {
|
| + if (mNodeProvider.dispatchHoverEvent(e)) {
|
| + return true;
|
| + }
|
| + }
|
| + return super.dispatchHoverEvent(e);
|
| + }
|
| +
|
| + /**
|
| + * @return The {@link LayoutManager} associated with this view.
|
| + */
|
| + public LayoutManager getLayoutManager() {
|
| + return mLayoutManager;
|
| + }
|
| +
|
| + /**
|
| + * @return The SurfaceView used by the Compositor.
|
| + */
|
| + public SurfaceView getSurfaceView() {
|
| + return mCompositorView;
|
| + }
|
| +
|
| + @Override
|
| + protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
| + super.onSizeChanged(w, h, oldw, oldh);
|
| + if (mLayoutManager == null) return;
|
| +
|
| + sCachedViewList.clear();
|
| + mLayoutManager.getActiveLayout().getAllViews(sCachedViewList);
|
| +
|
| + boolean resized = false;
|
| + for (int i = 0; i < sCachedViewList.size(); i++) {
|
| + resized |= setSizeOfUnattachedView(sCachedViewList.get(i));
|
| + }
|
| + sCachedViewList.clear();
|
| +
|
| + if (resized) requestRender();
|
| + }
|
| +
|
| + @Override
|
| + public void onPhysicalBackingSizeChanged(int width, int height) {
|
| + if (mLayoutManager == null) return;
|
| +
|
| + sCachedCVCList.clear();
|
| + mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCList);
|
| +
|
| + for (int i = 0; i < sCachedCVCList.size(); i++) {
|
| + sCachedCVCList.get(i).onPhysicalBackingSizeChanged(width, height);
|
| + }
|
| + sCachedCVCList.clear();
|
| + }
|
| +
|
| + @Override
|
| + public void onOverdrawBottomHeightChanged(int overdrawHeight) {
|
| + if (mLayoutManager == null) return;
|
| +
|
| + sCachedCVCList.clear();
|
| + mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCList);
|
| +
|
| + for (int i = 0; i < sCachedCVCList.size(); i++) {
|
| + sCachedCVCList.get(i).onOverdrawBottomHeightChanged(overdrawHeight);
|
| + }
|
| + sCachedCVCList.clear();
|
| +
|
| + mSkipNextToolbarTextureUpdate = true;
|
| + requestRender();
|
| + }
|
| +
|
| + @Override
|
| + public int getCurrentOverdrawBottomHeight() {
|
| + if (mTabVisible != null) {
|
| + float overdrawBottomHeight = mTabVisible.getFullscreenOverdrawBottomHeightPix();
|
| + if (!Float.isNaN(overdrawBottomHeight)) {
|
| + return (int) overdrawBottomHeight;
|
| + }
|
| + }
|
| + return mCompositorView.getOverdrawBottomHeight();
|
| + }
|
| +
|
| + /**
|
| + * Called whenever the host activity is started.
|
| + */
|
| + public void onStart() {
|
| + if (mFullscreenManager != null) {
|
| + mLastContentOffset = mFullscreenManager.getContentOffset();
|
| + mLastVisibleContentOffset = mFullscreenManager.getVisibleContentOffset();
|
| + mFullscreenManager.addListener(this);
|
| + }
|
| + requestRender();
|
| + }
|
| +
|
| + /**
|
| + * Called whenever the host activity is stopped.
|
| + */
|
| + public void onStop() {
|
| + if (mFullscreenManager != null) mFullscreenManager.removeListener(this);
|
| + }
|
| +
|
| + @Override
|
| + public void onContentOffsetChanged(float offset) {
|
| + mLastContentOffset = offset;
|
| + propagateViewportToLayouts(getWidth(), getHeight());
|
| + }
|
| +
|
| + @Override
|
| + public void onVisibleContentOffsetChanged(float offset) {
|
| + mLastVisibleContentOffset = offset;
|
| + propagateViewportToLayouts(getWidth(), getHeight());
|
| + requestRender();
|
| + }
|
| +
|
| + @Override
|
| + public void onToggleOverlayVideoMode(boolean enabled) {
|
| + if (mCompositorView != null) {
|
| + mCompositorView.setOverlayVideoMode(enabled);
|
| + }
|
| + }
|
| +
|
| + private void setContentViewMotionEventOffsets(MotionEvent e, boolean canClear) {
|
| + // TODO(dtrainor): Factor this out to LayoutDriver.
|
| + if (e == null || mTabVisible == null) return;
|
| +
|
| + ContentViewCore contentViewCore = mTabVisible.getContentViewCore();
|
| + if (contentViewCore == null) return;
|
| +
|
| + int actionMasked = e.getActionMasked();
|
| +
|
| + if (SPenSupport.isSPenSupported(getContext())) {
|
| + actionMasked = SPenSupport.convertSPenEventAction(actionMasked);
|
| + }
|
| +
|
| + if (actionMasked == MotionEvent.ACTION_DOWN
|
| + || actionMasked == MotionEvent.ACTION_HOVER_ENTER) {
|
| + if (mLayoutManager != null) mLayoutManager.getViewportPixel(mCacheViewport);
|
| + contentViewCore.setCurrentMotionEventOffsets(-mCacheViewport.left, -mCacheViewport.top);
|
| + } else if (canClear && (actionMasked == MotionEvent.ACTION_UP
|
| + || actionMasked == MotionEvent.ACTION_CANCEL
|
| + || actionMasked == MotionEvent.ACTION_HOVER_EXIT)) {
|
| + contentViewCore.setCurrentMotionEventOffsets(0.f, 0.f);
|
| + }
|
| + }
|
| +
|
| + private void propagateViewportToLayouts(int contentWidth, int contentHeight) {
|
| + int heightMinusTopControls = contentHeight - getTopControlsHeightPixels();
|
| + mCacheViewport.set(0, (int) mLastContentOffset, contentWidth, contentHeight);
|
| + mCacheVisibleViewport.set(0, (int) mLastVisibleContentOffset, contentWidth, contentHeight);
|
| + // TODO(changwan): check if this can be merged with setContentMotionEventOffsets.
|
| + if (mTabVisible != null && mTabVisible.getContentViewCore() != null) {
|
| + mTabVisible.getContentViewCore().setSmartClipOffsets(
|
| + -mCacheViewport.left, -mCacheViewport.top);
|
| + }
|
| + if (mLayoutManager != null) {
|
| + mLayoutManager.pushNewViewport(
|
| + mCacheViewport, mCacheVisibleViewport, heightMinusTopControls);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * To be called once a frame before commit.
|
| + */
|
| + @Override
|
| + public void onCompositorLayout() {
|
| + TraceEvent.begin("CompositorViewHolder:layout");
|
| + if (mLayoutManager != null) {
|
| + mLayoutManager.onUpdate();
|
| + mCompositorView.finalizeLayers(mLayoutManager, mSkipNextToolbarTextureUpdate);
|
| + // TODO(changwan): Check if this hack can be removed.
|
| + // This is a hack to draw one more frame if the screen just rotated for Nexus 10 + L.
|
| + // See http://crbug/440469 for more.
|
| + if (mSkipNextToolbarTextureUpdate) {
|
| + requestRender();
|
| + }
|
| + }
|
| +
|
| + TraceEvent.end("CompositorViewHolder:layout");
|
| + mSkipNextToolbarTextureUpdate = false;
|
| + }
|
| +
|
| + @Override
|
| + public void requestRender() {
|
| + mCompositorView.requestRender();
|
| + }
|
| +
|
| + @Override
|
| + public void onSurfaceCreated() {
|
| + mPendingSwapBuffersCount = 0;
|
| + flushInvalidation();
|
| + }
|
| +
|
| + @Override
|
| + public void onSwapBuffersCompleted(int pendingSwapBuffersCount) {
|
| + TraceEvent.instant("onSwapBuffersCompleted");
|
| +
|
| + // Wait until the second frame to turn off the placeholder background on
|
| + // tablets so the tab strip has time to start drawing.
|
| + final ViewGroup controlContainer = (ViewGroup) mControlContainer;
|
| + if (controlContainer != null && controlContainer.getBackground() != null && mHasDrawnOnce) {
|
| + post(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + controlContainer.setBackgroundResource(0);
|
| + }
|
| + });
|
| + }
|
| +
|
| + mHasDrawnOnce = true;
|
| +
|
| + mPendingSwapBuffersCount = pendingSwapBuffersCount;
|
| +
|
| + if (!mSkipInvalidation || pendingSwapBuffersCount == 0) flushInvalidation();
|
| + mSkipInvalidation = !mSkipInvalidation;
|
| + }
|
| +
|
| + @Override
|
| + public void setContentOverlayVisibility(boolean show) {
|
| + if (show != mContentOverlayVisiblity) {
|
| + mContentOverlayVisiblity = show;
|
| + updateContentOverlayVisibility(mContentOverlayVisiblity);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public LayoutRenderHost getLayoutRenderHost() {
|
| + return this;
|
| + }
|
| +
|
| + @Override
|
| + public int getLayoutTabsDrawnCount() {
|
| + return mCompositorView.getLastLayerCount();
|
| + }
|
| +
|
| + @Override
|
| + public void pushDebugRect(Rect rect, int color) {
|
| + if (mDebugOverlay == null) {
|
| + mDebugOverlay = new DebugOverlay(getContext());
|
| + addView(mDebugOverlay);
|
| + }
|
| + mDebugOverlay.pushRect(rect, color);
|
| + }
|
| +
|
| + @Override
|
| + public void loadPersitentTextureDataIfNeeded() {}
|
| +
|
| + @Override
|
| + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
| + super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
| + mIsKeyboardShowing = UiUtils.isKeyboardShowing(getContext(), this);
|
| + }
|
| +
|
| + @Override
|
| + protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
| + if (changed) {
|
| + propagateViewportToLayouts(r - l, b - t);
|
| + }
|
| + super.onLayout(changed, l, t, r, b);
|
| +
|
| + invalidateAccessibilityProvider();
|
| + }
|
| +
|
| + @Override
|
| + public void clearChildFocus(View child) {
|
| + // Override this method so that the ViewRoot doesn't go looking for a new
|
| + // view to take focus. It will find the URL Bar, focus it, then refocus this
|
| + // later, causing a keyboard flicker.
|
| + }
|
| +
|
| + @Override
|
| + public ChromeFullscreenManager getFullscreenManager() {
|
| + return mFullscreenManager;
|
| + }
|
| +
|
| + /**
|
| + * Sets a fullscreen handler.
|
| + * @param fullscreen A fullscreen handler.
|
| + */
|
| + public void setFullscreenHandler(ChromeFullscreenManager fullscreen) {
|
| + mFullscreenManager = fullscreen;
|
| + if (mFullscreenManager != null) {
|
| + mLastContentOffset = mFullscreenManager.getContentOffset();
|
| + mLastVisibleContentOffset = mFullscreenManager.getVisibleContentOffset();
|
| + mFullscreenManager.addListener(this);
|
| + }
|
| + propagateViewportToLayouts(getWidth(), getHeight());
|
| + }
|
| +
|
| + /**
|
| + * Note that the returned rect is reused for other calls.
|
| + */
|
| + @Override
|
| + public Rect getVisibleViewport(Rect rect) {
|
| + if (rect == null) rect = new Rect();
|
| + rect.set(0, (int) mLastVisibleContentOffset, getWidth(), getHeight());
|
| + return rect;
|
| + }
|
| +
|
| + @Override
|
| + public boolean areTopControlsPermanentlyHidden() {
|
| + return mFullscreenManager != null && mFullscreenManager.areTopControlsPermanentlyHidden();
|
| + }
|
| +
|
| + @Override
|
| + public int getTopControlsHeightPixels() {
|
| + return mFullscreenManager != null ? mFullscreenManager.getTopControlsHeight() : 0;
|
| + }
|
| +
|
| + /**
|
| + * Sets the URL bar. This is needed so that the ContentViewHolder can find out
|
| + * whether it can claim focus.
|
| + */
|
| + public void setUrlBar(View urlBar) {
|
| + mUrlBar = urlBar;
|
| + }
|
| +
|
| + @Override
|
| + protected void onAttachedToWindow() {
|
| + mInvalidator.set(this);
|
| + super.onAttachedToWindow();
|
| + }
|
| +
|
| + @Override
|
| + protected void onDetachedFromWindow() {
|
| + if (mLayoutManager != null) mLayoutManager.destroy();
|
| + flushInvalidation();
|
| + mInvalidator.set(null);
|
| + super.onDetachedFromWindow();
|
| +
|
| + // Removes the accessibility node provider from this view.
|
| + if (mNodeProvider != null) {
|
| + mAccessibilityView.setAccessibilityDelegate(null);
|
| + mNodeProvider = null;
|
| + removeView(mAccessibilityView);
|
| + mAccessibilityView = null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @return True if the currently active content view is shown in the normal interactive mode.
|
| + */
|
| + public boolean isTabInteractive() {
|
| + return mLayoutManager != null && mLayoutManager.getActiveLayout() != null
|
| + && mLayoutManager.getActiveLayout().isTabInteractive() && mContentOverlayVisiblity
|
| + && mView != null;
|
| + }
|
| +
|
| + /**
|
| + * Hides the the keyboard if it was opened for the ContentView.
|
| + * @param postHideTask A task to run after the keyboard is done hiding and the view's
|
| + * layout has been updated. If the keyboard was not shown, the task will run
|
| + * immediately.
|
| + */
|
| + public void hideKeyboard(Runnable postHideTask) {
|
| + // When this is called we actually want to hide the keyboard whatever owns it.
|
| + // This includes hiding the keyboard, and dropping focus from the URL bar.
|
| + // See http://crbug/236424
|
| + // TODO(aberent) Find a better place to put this, possibly as part of a wider
|
| + // redesign of focus control.
|
| + if (mUrlBar != null) mUrlBar.clearFocus();
|
| + boolean wasVisible = false;
|
| + if (hasFocus()) {
|
| + wasVisible = UiUtils.hideKeyboard(this);
|
| + }
|
| + if (wasVisible) {
|
| + mPostHideKeyboardTask = postHideTask;
|
| + } else {
|
| + postHideTask.run();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Sets the appropriate objects this class should represent.
|
| + * @param tabModelSelector The {@link TabModelSelector} this View should hold and
|
| + * represent.
|
| + * @param tabCreatorManager The {@link TabCreatorManager} for this view.
|
| + * @param tabContentManager The {@link TabContentManager} for the tabs.
|
| + * @param androidContentContainer The {@link ViewGroup} the {@link LayoutManager} should bind
|
| + * Android content to.
|
| + * @param contextualSearchManager A {@link ContextualSearchManagementDelegate} instance.
|
| + */
|
| + public void onFinishNativeInitialization(TabModelSelector tabModelSelector,
|
| + TabCreatorManager tabCreatorManager, TabContentManager tabContentManager,
|
| + ViewGroup androidContentContainer,
|
| + ContextualSearchManagementDelegate contextualSearchManager) {
|
| + assert mLayoutManager != null;
|
| + mLayoutManager.init(tabModelSelector, tabCreatorManager, tabContentManager,
|
| + androidContentContainer, contextualSearchManager,
|
| + mCompositorView.getResourceManager().getDynamicResourceLoader());
|
| + mTabModelSelector = tabModelSelector;
|
| + tabModelSelector.addObserver(new EmptyTabModelSelectorObserver() {
|
| + @Override
|
| + public void onChange() {
|
| + onContentChanged();
|
| + }
|
| +
|
| + @Override
|
| + public void onNewTabCreated(Tab tab) {
|
| + initializeTab(tab);
|
| + }
|
| + });
|
| +
|
| + onContentChanged();
|
| + }
|
| +
|
| + private void updateContentOverlayVisibility(boolean show) {
|
| + if (mView == null) return;
|
| +
|
| + sCachedCVCList.clear();
|
| + if (mLayoutManager != null) {
|
| + mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCList);
|
| + }
|
| + if (show) {
|
| + if (mView.getParent() != this) {
|
| + // Make sure the view isn't a child of something else before we attempt to add it.
|
| + if (mView.getParent() != null && mView.getParent() instanceof ViewGroup) {
|
| + ((ViewGroup) mView.getParent()).removeView(mView);
|
| + }
|
| +
|
| + for (int i = 0; i < sCachedCVCList.size(); i++) {
|
| + ContentViewCore content = sCachedCVCList.get(i);
|
| + assert content.isAlive();
|
| + content.getContainerView().setVisibility(View.VISIBLE);
|
| + if (mFullscreenManager != null) {
|
| + mFullscreenManager.updateContentViewViewportSize(content);
|
| + }
|
| + }
|
| +
|
| + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
| + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
| + addView(mView, layoutParams);
|
| + setFocusable(false);
|
| + setFocusableInTouchMode(false);
|
| +
|
| + // Claim focus for the new view unless the user is currently using the URL bar.
|
| + if (mUrlBar == null || !mUrlBar.hasFocus()) mView.requestFocus();
|
| + }
|
| + } else {
|
| + if (mView.getParent() == this) {
|
| + setFocusable(true);
|
| + setFocusableInTouchMode(true);
|
| +
|
| + for (int i = 0; i < sCachedCVCList.size(); i++) {
|
| + ContentViewCore content = sCachedCVCList.get(i);
|
| + if (content.isAlive()) content.getContainerView().setVisibility(View.INVISIBLE);
|
| + }
|
| +
|
| + if (hasFocus()) {
|
| + InputMethodManager manager = (InputMethodManager) getContext().getSystemService(
|
| + Context.INPUT_METHOD_SERVICE);
|
| + if (manager.isActive(this)) {
|
| + manager.hideSoftInputFromWindow(getWindowToken(), 0, null);
|
| + }
|
| + }
|
| + removeView(mView);
|
| + }
|
| + }
|
| + sCachedCVCList.clear();
|
| + }
|
| +
|
| + @Override
|
| + public void onContentChanged() {
|
| + if (mTabModelSelector == null) {
|
| + // Not yet initialized, onContentChanged() will eventually get called by
|
| + // setTabModelSelector.
|
| + return;
|
| + }
|
| + Tab tab = mTabModelSelector.getCurrentTab();
|
| + setTab(tab);
|
| + }
|
| +
|
| + @Override
|
| + public void onContentViewCoreAdded(ContentViewCore content) {
|
| + // TODO(dtrainor): Look into rolling this into onContentChanged().
|
| + initializeContentViewCore(content);
|
| + setSizeOfUnattachedView(content.getContainerView());
|
| + }
|
| +
|
| + private void setTab(Tab tab) {
|
| + if (tab != null && tab.isFrozen()) tab.unfreezeContents();
|
| +
|
| + View newView = tab != null ? tab.getView() : null;
|
| + if (mView == newView) return;
|
| +
|
| + // TODO(dtrainor): Look into changing this only if the views differ, but still parse the
|
| + // ContentViewCore list even if they're the same.
|
| + updateContentOverlayVisibility(false);
|
| +
|
| + if (mTabVisible != tab) {
|
| + if (mTabVisible != null) mTabVisible.removeObserver(mTabObserver);
|
| + if (tab != null) tab.addObserver(mTabObserver);
|
| + }
|
| +
|
| + mTabVisible = tab;
|
| + mView = newView;
|
| +
|
| + updateContentOverlayVisibility(mContentOverlayVisiblity);
|
| +
|
| + if (mTabVisible != null) initializeTab(mTabVisible);
|
| + }
|
| +
|
| + /**
|
| + * Sets the correct size for all {@link View}s on {@code tab} and sets the correct rendering
|
| + * parameters on all {@link ContentViewCore}s on {@code tab}.
|
| + * @param tab The {@link Tab} to initialize.
|
| + */
|
| + private void initializeTab(Tab tab) {
|
| + sCachedCVCList.clear();
|
| + if (mLayoutManager != null) {
|
| + mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCList);
|
| + }
|
| +
|
| + for (int i = 0; i < sCachedCVCList.size(); i++) {
|
| + initializeContentViewCore(sCachedCVCList.get(i));
|
| + }
|
| + sCachedCVCList.clear();
|
| +
|
| + sCachedViewList.clear();
|
| + tab.getAllViews(sCachedViewList);
|
| +
|
| + for (int i = 0; i < sCachedViewList.size(); i++) {
|
| + View view = sCachedViewList.get(i);
|
| + // Calling View#measure() and View#layout() on a View before adding it to the view
|
| + // hierarchy seems to cause issues with compound drawables on some versions of Android.
|
| + // We don't need to proactively size the NTP as we don't need the Android view to render
|
| + // if it's not actually attached to the view hierarchy (http://crbug.com/462114).
|
| + if (view == tab.getView() && tab.isNativePage()) continue;
|
| + setSizeOfUnattachedView(view);
|
| + }
|
| + sCachedViewList.clear();
|
| + }
|
| +
|
| + /**
|
| + * Initializes the rendering surface parameters of {@code contentViewCore}. Note that this does
|
| + * not size the actual {@link ContentViewCore}.
|
| + * @param contentViewCore The {@link ContentViewCore} to initialize.
|
| + */
|
| + private void initializeContentViewCore(ContentViewCore contentViewCore) {
|
| + contentViewCore.setCurrentMotionEventOffsets(0.f, 0.f);
|
| + contentViewCore.setTopControlsHeight(
|
| + getTopControlsHeightPixels(), contentViewCore.doTopControlsShrinkBlinkSize());
|
| + contentViewCore.onPhysicalBackingSizeChanged(
|
| + mCompositorView.getWidth(), mCompositorView.getHeight());
|
| + contentViewCore.onOverdrawBottomHeightChanged(mCompositorView.getOverdrawBottomHeight());
|
| + }
|
| +
|
| + /**
|
| + * Resize {@code view} to match the size of this {@link FrameLayout}. This will only happen if
|
| + * {@code view} is not {@code null} and if {@link View#getWindowToken()} returns {@code null}
|
| + * (the {@link View} is not part of the view hierarchy).
|
| + * @param view The {@link View} to resize.
|
| + * @return Whether or not {@code view} was resized.
|
| + */
|
| + private boolean setSizeOfUnattachedView(View view) {
|
| + // Need to call layout() for the following View if it is not attached to the view hierarchy.
|
| + // Calling onSizeChanged() is dangerous because if the View has a different size than the
|
| + // ContentViewCore it might think a future size update is a NOOP and not call
|
| + // onSizeChanged() on the ContentViewCore.
|
| + if (view == null || view.getWindowToken() != null) return false;
|
| + int width = getWidth();
|
| + int height = getHeight();
|
| + view.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
| + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
|
| + view.layout(0, 0, width, height);
|
| + return true;
|
| + }
|
| +
|
| + @Override
|
| + public TitleCache getTitleCache() {
|
| + return mLayerTitleCache;
|
| + }
|
| +
|
| + @Override
|
| + public void deferInvalidate(Client client) {
|
| + if (mPendingSwapBuffersCount <= 0) {
|
| + client.doInvalidate();
|
| + } else if (!mPendingInvalidations.contains(client)) {
|
| + mPendingInvalidations.add(client);
|
| + }
|
| + }
|
| +
|
| + private void flushInvalidation() {
|
| + if (mPendingInvalidations.isEmpty()) return;
|
| + TraceEvent.instant("CompositorViewHolder.flushInvalidation");
|
| + for (int i = 0; i < mPendingInvalidations.size(); i++) {
|
| + mPendingInvalidations.get(i).doInvalidate();
|
| + }
|
| + mPendingInvalidations.clear();
|
| + }
|
| +
|
| + @Override
|
| + public void invalidateAccessibilityProvider() {
|
| + if (mNodeProvider != null) {
|
| + mNodeProvider.invalidateRoot();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when the accessibility enabled state changes.
|
| + * @param enabled Whether accessibility is enabled.
|
| + */
|
| + public void onAccessibilityStatusChanged(boolean enabled) {
|
| + // Instantiate and install the accessibility node provider on this view if necessary.
|
| + // This overrides any hover event listeners or accessibility delegates
|
| + // that may have been added elsewhere.
|
| + if (enabled && (mNodeProvider == null)) {
|
| + mAccessibilityView = new View(getContext());
|
| + addView(mAccessibilityView);
|
| + mNodeProvider = new CompositorAccessibilityProvider(mAccessibilityView);
|
| + ViewCompat.setAccessibilityDelegate(mAccessibilityView, mNodeProvider);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Class used to provide a virtual view hierarchy to the Accessibility
|
| + * framework for this view and its contained items.
|
| + * <p>
|
| + * <strong>NOTE:</strong> This class is fully backwards compatible for
|
| + * compilation, but will only provide touch exploration on devices running
|
| + * Ice Cream Sandwich and above.
|
| + * </p>
|
| + */
|
| + private class CompositorAccessibilityProvider extends ExploreByTouchHelper {
|
| + private final float mDpToPx;
|
| + List<VirtualView> mVirtualViews = new ArrayList<VirtualView>();
|
| + private final Rect mPlaceHolderRect = new Rect(0, 0, 1, 1);
|
| + private static final String PLACE_HOLDER_STRING = "";
|
| + private final RectF mTouchTarget = new RectF();
|
| + private final Rect mPixelRect = new Rect();
|
| +
|
| + public CompositorAccessibilityProvider(View forView) {
|
| + super(forView);
|
| + mDpToPx = getContext().getResources().getDisplayMetrics().density;
|
| + }
|
| +
|
| + @Override
|
| + protected int getVirtualViewAt(float x, float y) {
|
| + if (mVirtualViews == null) return INVALID_ID;
|
| + for (int i = 0; i < mVirtualViews.size(); i++) {
|
| + if (mVirtualViews.get(i).checkClicked(x / mDpToPx, y / mDpToPx)) {
|
| + return i;
|
| + }
|
| + }
|
| + return INVALID_ID;
|
| + }
|
| +
|
| + @Override
|
| + protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
|
| + if (mLayoutManager == null) return;
|
| + mVirtualViews.clear();
|
| + mLayoutManager.getVirtualViews(mVirtualViews);
|
| + for (int i = 0; i < mVirtualViews.size(); i++) {
|
| + virtualViewIds.add(i);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + protected boolean onPerformActionForVirtualView(
|
| + int virtualViewId, int action, Bundle arguments) {
|
| + switch (action) {
|
| + case AccessibilityNodeInfoCompat.ACTION_CLICK:
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| + }
|
| +
|
| + @Override
|
| + protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
|
| + if (mVirtualViews == null || mVirtualViews.size() <= virtualViewId) {
|
| + // TODO(clholgat): Remove this work around when the Android bug is fixed.
|
| + // crbug.com/420177
|
| + event.setContentDescription(PLACE_HOLDER_STRING);
|
| + return;
|
| + }
|
| + VirtualView view = mVirtualViews.get(virtualViewId);
|
| +
|
| + event.setContentDescription(view.getAccessibilityDescription());
|
| + event.setClassName(CompositorViewHolder.class.getName());
|
| + }
|
| +
|
| + @Override
|
| + protected void onPopulateNodeForVirtualView(
|
| + int virtualViewId, AccessibilityNodeInfoCompat node) {
|
| + if (mVirtualViews == null || mVirtualViews.size() <= virtualViewId) {
|
| + // TODO(clholgat): Remove this work around when the Android bug is fixed.
|
| + // crbug.com/420177
|
| + node.setBoundsInParent(mPlaceHolderRect);
|
| + node.setContentDescription(PLACE_HOLDER_STRING);
|
| + return;
|
| + }
|
| + VirtualView view = mVirtualViews.get(virtualViewId);
|
| + view.getTouchTarget(mTouchTarget);
|
| +
|
| + node.setBoundsInParent(rectToPx(mTouchTarget));
|
| + node.setContentDescription(view.getAccessibilityDescription());
|
| + node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
|
| + node.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS);
|
| + node.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
|
| + }
|
| +
|
| + private Rect rectToPx(RectF rect) {
|
| + rect.roundOut(mPixelRect);
|
| + mPixelRect.left = (int) (mPixelRect.left * mDpToPx);
|
| + mPixelRect.top = (int) (mPixelRect.top * mDpToPx);
|
| + mPixelRect.right = (int) (mPixelRect.right * mDpToPx);
|
| + mPixelRect.bottom = (int) (mPixelRect.bottom * mDpToPx);
|
| +
|
| + // Don't let any zero sized rects through, they'll cause parent
|
| + // size errors in L.
|
| + if (mPixelRect.width() == 0) {
|
| + mPixelRect.right = mPixelRect.left + 1;
|
| + }
|
| + if (mPixelRect.height() == 0) {
|
| + mPixelRect.bottom = mPixelRect.top + 1;
|
| + }
|
| + return mPixelRect;
|
| + }
|
| + }
|
| +}
|
|
|