// 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.toolbar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
import org.chromium.chrome.browser.contextualsearch.SwipeRecognizer;
import org.chromium.chrome.browser.util.ViewUtils;
import org.chromium.chrome.browser.widget.ClipDrawableProgressBar.DrawingInfo;
import org.chromium.chrome.browser.widget.ControlContainer;
import org.chromium.chrome.browser.widget.ToolbarProgressBar;
import org.chromium.chrome.browser.widget.ViewResourceFrameLayout;
import org.chromium.ui.UiUtils;
import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
/**
* Layout for the browser controls (omnibox, menu, tab strip, etc..).
*/
public class ToolbarControlContainer extends FrameLayout implements ControlContainer {
private final float mTabStripHeight;
private Toolbar mToolbar;
private ToolbarViewResourceFrameLayout mToolbarContainer;
private View mMenuBtn;
private final SwipeRecognizer mSwipeRecognizer;
private EdgeSwipeHandler mSwipeHandler;
/**
* Constructs a new control container.
* <p>
* This constructor is used when inflating from XML.
*
* @param context The context used to build this view.
* @param attrs The attributes used to determine how to construct this view.
*/
public ToolbarControlContainer(Context context, AttributeSet attrs) {
super(context, attrs);
mTabStripHeight = context.getResources().getDimension(R.dimen.tab_strip_height);
mSwipeRecognizer = new SwipeRecognizerImpl(context);
}
@Override
public ViewResourceAdapter getToolbarResourceAdapter() {
return mToolbarContainer.getResourceAdapter();
}
@Override
public View getView() {
return this;
}
@Override
public void getProgressBarDrawingInfo(DrawingInfo drawingInfoOut) {
// TODO(yusufo): Avoid casting to the layout without making the interface bigger.
ToolbarProgressBar progressBar = ((ToolbarLayout) mToolbar).getProgressBar();
if (progressBar != null) progressBar.getDrawingInfo(drawingInfoOut);
}
@Override
public int getToolbarBackgroundColor() {
return ((ToolbarLayout) mToolbar).getToolbarDataProvider().getPrimaryColor();
}
@Override
public void setSwipeHandler(EdgeSwipeHandler handler) {
mSwipeHandler = handler;
mSwipeRecognizer.setSwipeHandler(handler);
}
@Override
public void onFinishInflate() {
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mToolbarContainer = (ToolbarViewResourceFrameLayout) findViewById(R.id.toolbar_container);
mMenuBtn = findViewById(R.id.menu_button);
if (mToolbar instanceof ToolbarTablet) {
// On tablet, draw a fake tab strip and toolbar until the compositor is ready to draw
// the real tab strip. (On phone, the toolbar is made entirely of Android views, which
// are already initialized.)
setBackgroundResource(R.drawable.toolbar_background);
}
assert mToolbar != null;
assert mMenuBtn != null;
super.onFinishInflate();
}
@Override
public boolean gatherTransparentRegion(Region region) {
// Reset the translation on the control container before attempting to compute the
// transparent region.
float translateY = getTranslationY();
setTranslationY(0);
ViewUtils.gatherTransparentRegionsForOpaqueView(this, region);
setTranslationY(translateY);
return true;
}
/**
* Invalidate the entire capturing bitmap region.
*/
public void invalidateBitmap() {
((ToolbarViewResourceAdapter) getToolbarResourceAdapter()).forceInvalidate();
}
/**
* Update whether the control container is ready to have the bitmap representation of
* itself be captured.
*/
public void setReadyForBitmapCapture(boolean ready) {
mToolbarContainer.mReadyForBitmapCapture = ready;
}
/**
* The layout that handles generating the toolbar view resource.
*/
// Only publicly visible due to lint warnings.
public static class ToolbarViewResourceFrameLayout extends ViewResourceFrameLayout {
private boolean mReadyForBitmapCapture;
public ToolbarViewResourceFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected ViewResourceAdapter createResourceAdapter() {
return new ToolbarViewResourceAdapter(
this, (Toolbar) findViewById(R.id.toolbar));
}
@Override
protected boolean isReadyForCapture() {
return mReadyForBitmapCapture;
}
}
private static class ToolbarViewResourceAdapter extends ViewResourceAdapter {
private final int mToolbarActualHeightPx;
private final int mTabStripHeightPx;
private final int[] mTempPosition = new int[2];
private final View mToolbarContainer;
private final Toolbar mToolbar;
/** Builds the resource adapter for the toolbar. */
public ToolbarViewResourceAdapter(View toolbarContainer, Toolbar toolbar) {
super(toolbarContainer);
mToolbarContainer = toolbarContainer;
mToolbar = toolbar;
int containerHeightResId = R.dimen.control_container_height;
if (mToolbar instanceof CustomTabToolbar) {
containerHeightResId = R.dimen.custom_tabs_control_container_height;
}
mToolbarActualHeightPx = toolbarContainer.getResources().getDimensionPixelSize(
containerHeightResId);
mTabStripHeightPx = toolbarContainer.getResources().getDimensionPixelSize(
R.dimen.tab_strip_height);
}
/**
* Force this resource to be recaptured in full, ignoring the checks
* {@link #invalidate(Rect)} does.
*/
public void forceInvalidate() {
super.invalidate(null);
}
@Override
public boolean isDirty() {
return mToolbar != null && mToolbar.isReadyForTextureCapture() && super.isDirty();
}
@Override
protected void onCaptureStart(Canvas canvas, Rect dirtyRect) {
// Erase the canvas because assets drawn are not fully opaque and therefore painting
// twice would be bad.
canvas.save();
canvas.clipRect(
0, 0,
mToolbarContainer.getWidth(), mToolbarContainer.getHeight());
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
canvas.restore();
dirtyRect.set(0, 0, mToolbarContainer.getWidth(), mToolbarContainer.getHeight());
mToolbar.setTextureCaptureMode(true);
super.onCaptureStart(canvas, dirtyRect);
}
@Override
protected void onCaptureEnd() {
mToolbar.setTextureCaptureMode(false);
// Forcing a texture capture should only be done for one draw. Turn off forced
// texture capture.
mToolbar.setForceTextureCapture(false);
}
@Override
protected void computeContentPadding(Rect outContentPadding) {
outContentPadding.set(0, mTabStripHeightPx, mToolbarContainer.getWidth(),
mToolbarActualHeightPx);
}
@Override
protected void computeContentAperture(Rect outContentAperture) {
mToolbar.getLocationBarContentRect(outContentAperture);
mToolbar.getPositionRelativeToContainer(mToolbarContainer, mTempPosition);
outContentAperture.offset(mTempPosition[0], mTempPosition[1]);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Don't eat the event if we don't have a handler.
if (mSwipeHandler == null) return false;
// If we have ACTION_DOWN in this context, that means either no child consumed the event or
// this class is the top UI at the event position. Then, we don't need to feed the event to
// mGestureDetector here because the event is already once fed in onInterceptTouchEvent().
// Moreover, we have to return true so that this class can continue to intercept all the
// subsequent events.
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && !isOnTabStrip(event)) {
return true;
}
return mSwipeRecognizer.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mSwipeHandler == null) return false;
return mSwipeRecognizer.onTouchEvent(event);
}
private boolean isOnTabStrip(MotionEvent e) {
return e.getY() <= mTabStripHeight;
}
private class SwipeRecognizerImpl extends SwipeRecognizer {
public SwipeRecognizerImpl(Context context) {
super(context);
}
@Override
public boolean shouldRecognizeSwipe(MotionEvent e1, MotionEvent e2) {
if (isOnTabStrip(e1)) return false;
if (mToolbar.shouldIgnoreSwipeGesture()) return false;
if (UiUtils.isKeyboardShowing(getContext(), ToolbarControlContainer.this)) return false;
return true;
}
}
}