blob: 8b246f5ee77255b917347372e9b28cde153997f2 [file] [log] [blame]
// Copyright 2019 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.weblayer_private;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.widget.FrameLayout;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.EventOffsetHandler;
import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
/**
* TopControlsContainerView is responsible for holding the top-view from the client. Further, it
* has a ViewResourceAdapter that is kept in sync with the contents of the top-view.
* ViewResourceAdapter is used to keep a bitmap in sync with the contents of the top-view. The
* bitmap is placed in a cc::Layer and the layer is shown while scrolling the top-view.
* ViewResourceAdapter is always kept in sync, as to do otherwise results in a noticeable delay
* between when the scroll starts the content is available.
*/
@JNINamespace("weblayer")
class TopControlsContainerView extends FrameLayout {
// ID used with ViewResourceAdapter.
private static final int TOP_CONTROLS_ID = 1001;
private long mNativeTopControlsContainerView;
private ViewResourceAdapter mViewResourceAdapter;
// Last width/height of mView as sent to the native side.
private int mLastWidth;
private int mLastHeight;
// view from the client.
private View mView;
private ContentViewRenderView mContentViewRenderView;
private WebContents mWebContents;
private EventOffsetHandler mEventOffsetHandler;
private int mTopContentOffset;
// True if scrolling.
private boolean mInTopControlsScroll;
// Used to delay updating the image for the layer.
private final Runnable mRefreshResourceIdRunnable = () -> {
TopControlsContainerViewJni.get().updateTopControlsResource(
mNativeTopControlsContainerView, TopControlsContainerView.this);
};
TopControlsContainerView(
Context context, WebContents webContents, ContentViewRenderView contentViewRenderView) {
super(context);
mContentViewRenderView = contentViewRenderView;
mWebContents = webContents;
mEventOffsetHandler =
new EventOffsetHandler(new EventOffsetHandler.EventOffsetHandlerDelegate() {
@Override
public float getTop() {
return mTopContentOffset;
}
@Override
public void setCurrentTouchEventOffsets(float top) {
mWebContents.getEventForwarder().setCurrentTouchEventOffsets(0, top);
}
});
mNativeTopControlsContainerView =
TopControlsContainerViewJni.get().createTopControlsContainerView(
webContents, contentViewRenderView.getNativeHandle());
}
public void destroy() {
setView(null);
TopControlsContainerViewJni.get().deleteTopControlsContainerView(
mNativeTopControlsContainerView, TopControlsContainerView.this);
}
public long getNativeHandle() {
return mNativeTopControlsContainerView;
}
public EventOffsetHandler getEventOffsetHandler() {
return mEventOffsetHandler;
}
/**
* Sets the view from the client.
*/
public void setView(View view) {
if (mView == view) return;
if (mView != null) {
if (mView.getParent() == this) removeView(mView);
// TODO: need some sort of destroy to drop reference.
mViewResourceAdapter = null;
TopControlsContainerViewJni.get().deleteTopControlsLayer(
mNativeTopControlsContainerView, TopControlsContainerView.this);
mContentViewRenderView.getResourceManager()
.getDynamicResourceLoader()
.unregisterResource(TOP_CONTROLS_ID);
}
mView = view;
if (mView == null) return;
addView(view,
new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.UNSPECIFIED_GRAVITY));
if (getWidth() > 0 && getHeight() > 0) {
view.layout(0, 0, getWidth(), getHeight());
createAdapterAndLayer();
}
}
public View getView() {
return mView;
}
/**
* Called from ViewAndroidDelegate, see it for details.
*/
public void onTopControlsChanged(int topControlsOffsetY, int topContentOffsetY) {
if (mView == null) return;
if (topContentOffsetY == getHeight()) {
finishTopControlsScroll(topContentOffsetY);
return;
}
if (!mInTopControlsScroll) prepareForTopControlsScroll();
setTopControlsOffset(topControlsOffsetY, topContentOffsetY);
}
@SuppressLint("NewApi") // Used on O+, invalidateChildInParent used for previous versions.
@Override
public void onDescendantInvalidated(View child, View target) {
super.onDescendantInvalidated(child, target);
invalidateViewResourceAdapter();
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
invalidateViewResourceAdapter();
return super.invalidateChildInParent(location, dirty);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mView == null) return;
int width = right - left;
int height = bottom - top;
if (height != mLastHeight || width != mLastWidth) {
mLastWidth = width;
mLastHeight = height;
if (mLastWidth > 0 && mLastHeight > 0) {
if (mViewResourceAdapter == null) {
createAdapterAndLayer();
} else {
TopControlsContainerViewJni.get().setTopControlsSize(
mNativeTopControlsContainerView, TopControlsContainerView.this,
mLastWidth, mLastHeight);
}
}
}
}
/**
* Triggers copying the contents of mView to the offscreen buffer.
*/
private void invalidateViewResourceAdapter() {
if (mViewResourceAdapter == null || mView.getVisibility() != View.VISIBLE) return;
mViewResourceAdapter.invalidate(null);
removeCallbacks(mRefreshResourceIdRunnable);
postOnAnimation(mRefreshResourceIdRunnable);
}
/**
* Creates mViewResourceAdapter and the layer showing a copy of mView.
*/
private void createAdapterAndLayer() {
assert mViewResourceAdapter == null;
assert mView != null;
mViewResourceAdapter = new ViewResourceAdapter(mView);
mContentViewRenderView.getResourceManager().getDynamicResourceLoader().registerResource(
TOP_CONTROLS_ID, mViewResourceAdapter);
// It's important that the layer is created immediately and always kept in sync with the
// View. Creating the layer only when needed results in a noticeable delay between when
// the layer is created and actually shown. Chrome for Android does the same thing.
TopControlsContainerViewJni.get().createTopControlsLayer(
mNativeTopControlsContainerView, TopControlsContainerView.this, TOP_CONTROLS_ID);
mLastWidth = getWidth();
mLastHeight = getHeight();
TopControlsContainerViewJni.get().setTopControlsSize(mNativeTopControlsContainerView,
TopControlsContainerView.this, mLastWidth, mLastHeight);
}
private void finishTopControlsScroll(int topContentOffsetY) {
mInTopControlsScroll = false;
setTopControlsOffset(0, topContentOffsetY);
mContentViewRenderView.postOnAnimation(() -> showTopControls());
}
private void setTopControlsOffset(int topControlsOffsetY, int topContentOffsetY) {
mTopContentOffset = topContentOffsetY;
TopControlsContainerViewJni.get().setTopControlsOffset(mNativeTopControlsContainerView,
TopControlsContainerView.this, topControlsOffsetY, topContentOffsetY);
}
private void prepareForTopControlsScroll() {
mInTopControlsScroll = true;
mContentViewRenderView.postOnAnimation(() -> hideTopControls());
}
private void hideTopControls() {
if (mView != null) mView.setVisibility(View.INVISIBLE);
}
private void showTopControls() {
if (mView != null) mView.setVisibility(View.VISIBLE);
}
@NativeMethods
interface Natives {
long createTopControlsContainerView(
WebContents webContents, long nativeContentViewRenderView);
void deleteTopControlsContainerView(
long nativeTopControlsContainerView, TopControlsContainerView caller);
void createTopControlsLayer(
long nativeTopControlsContainerView, TopControlsContainerView caller, int id);
void deleteTopControlsLayer(
long nativeTopControlsContainerView, TopControlsContainerView caller);
void setTopControlsOffset(long nativeTopControlsContainerView,
TopControlsContainerView caller, int topControlsOffsetY, int topContentOffsetY);
void setTopControlsSize(long nativeTopControlsContainerView,
TopControlsContainerView caller, int width, int height);
void updateTopControlsResource(
long nativeTopControlsContainerView, TopControlsContainerView caller);
}
}