Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1747)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698