Skip to content
This repository has been archived by the owner on Apr 3, 2020. It is now read-only.

Commit

Permalink
[WebView] Hide PopupWindow-based selection handles on scroll
Browse files Browse the repository at this point in the history
Restore the previous behavior of hiding the PopupWindow-based text
selection handles on scroll. Note that such hiding applies when the
content is scrolling as well as when the containing View is moving.

Also avoid sync barrier issues by deferring invalidations relating to
visibility and position updates of the PopupWindow.

BUG=423120

Review URL: https://codereview.chromium.org/657593003

Cr-Commit-Position: refs/heads/master@{#300114}
  • Loading branch information
jdduke authored and Commit bot committed Oct 17, 2014
1 parent c7d6272 commit 4a73ea3
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2187,6 +2187,11 @@ public boolean onTouchHandleEvent(MotionEvent event) {
final boolean isTouchHandleEvent = true;
return onTouchEventImpl(event, isTouchHandleEvent);
}

@Override
public boolean isScrollInProgress() {
return ContentViewCore.this.isScrollInProgress();
}
};
}
return new PopupTouchHandleDrawable(mTouchHandleDelegate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.PopupWindow;

import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.content.browser.PositionObserver;
Expand Down Expand Up @@ -60,6 +62,19 @@ public class PopupTouchHandleDrawable extends View {
static final int RIGHT = 2;
private int mOrientation = -1;

// Length of the delay before fading in after the last page movement.
private static final int FADE_IN_DELAY_MS = 300;
private static final int FADE_IN_DURATION_MS = 200;
private Runnable mDeferredHandleFadeInRunnable;
private long mFadeStartTime;
private boolean mVisible;
private boolean mTemporarilyHidden;

// Deferred runnable to avoid invalidating outside of frame dispatch,
// in turn avoiding issues with sync barrier insertion.
private Runnable mInvalidationRunnable;
private boolean mHasPendingInvalidate;

/**
* Provides additional interaction behaviors necessary for handle
* manipulation and interaction.
Expand All @@ -81,6 +96,11 @@ public interface PopupTouchHandleDrawableDelegate {
* performing handle manipulation.
*/
boolean onTouchHandleEvent(MotionEvent ev);

/**
* @return Whether the associated content is actively scrolling.
*/
boolean isScrollInProgress();
}

public PopupTouchHandleDrawable(PopupTouchHandleDrawableDelegate delegate) {
Expand All @@ -90,7 +110,9 @@ public PopupTouchHandleDrawable(PopupTouchHandleDrawableDelegate delegate) {
mContainer = new PopupWindow(mContext, null, android.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
mContainer.setClippingEnabled(false);
mContainer.setAnimationStyle(0);
mAlpha = 1.f;
mVisible = getVisibility() == VISIBLE;
mParentPositionListener = new PositionObserver.Listener() {
@Override
public void onPositionChanged(int x, int y) {
Expand Down Expand Up @@ -153,16 +175,16 @@ private void setOrientation(int orientation) {
mHotspotY = 0;

// Force handle repositioning to accommodate the new orientation's hotspot.
if (hadValidOrientation) positionAt(oldAdjustedPositionX, oldAdjustedPositionY);
if (hadValidOrientation) setFocus(oldAdjustedPositionX, oldAdjustedPositionY);
mDrawable.setAlpha((int) (255 * mAlpha));

invalidate();
scheduleInvalidate();
}

private void updateParentPosition(int parentPositionX, int parentPositionY) {
if (mParentPositionX == parentPositionX && mParentPositionY == parentPositionY) return;
mParentPositionX = parentPositionX;
mParentPositionY = parentPositionY;
onPositionChanged();
temporarilyHide();
}

private int getContainerPositionX() {
Expand All @@ -173,16 +195,77 @@ private int getContainerPositionY() {
return mParentPositionY + mPositionY;
}

private void onPositionChanged() {
private void updatePosition() {
mContainer.update(getContainerPositionX(), getContainerPositionY(),
getRight() - getLeft(), getBottom() - getTop());
}

// x and y are in physical pixels.
private void moveTo(int x, int y) {
mPositionX = x;
mPositionY = y;
onPositionChanged();
private void updateVisibility() {
boolean visible = mVisible && !mTemporarilyHidden;
setVisibility(visible ? VISIBLE : INVISIBLE);
}

private void updateAlpha() {
if (mAlpha == 1.f) return;
long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
mAlpha = Math.min(1.f, (float) (currentTimeMillis - mFadeStartTime) / FADE_IN_DURATION_MS);
mDrawable.setAlpha((int) (255 * mAlpha));
scheduleInvalidate();
}

private void temporarilyHide() {
mTemporarilyHidden = true;
updateVisibility();
rescheduleFadeIn();
}

private void doInvalidate() {
updatePosition();
updateVisibility();
invalidate();
}

private void scheduleInvalidate() {
if (mInvalidationRunnable == null) {
mInvalidationRunnable = new Runnable() {
@Override
public void run() {
mHasPendingInvalidate = false;
doInvalidate();
}
};
}

if (mHasPendingInvalidate) return;
mHasPendingInvalidate = true;
ApiCompatibilityUtils.postOnAnimation(this, mInvalidationRunnable);
}

private void rescheduleFadeIn() {
if (mDeferredHandleFadeInRunnable == null) {
mDeferredHandleFadeInRunnable = new Runnable() {
@Override
public void run() {
if (isScrollInProgress()) {
rescheduleFadeIn();
return;
}
mTemporarilyHidden = false;
beginFadeIn();
}
};
}

removeCallbacks(mDeferredHandleFadeInRunnable);
ApiCompatibilityUtils.postOnAnimationDelayed(
this, mDeferredHandleFadeInRunnable, FADE_IN_DELAY_MS);
}

private void beginFadeIn() {
if (getVisibility() == VISIBLE) return;
mAlpha = 0.f;
mFadeStartTime = AnimationUtils.currentAnimationTimeMillis();
doInvalidate();
}

@Override
Expand All @@ -191,22 +274,17 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
return;
}
setMeasuredDimension(mDrawable.getIntrinsicWidth(),
mDrawable.getIntrinsicHeight());
setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
}

@Override
protected void onDraw(Canvas c) {
if (mDrawable == null) return;
updateAlpha();
mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
mDrawable.draw(c);
}

// x and y are in physical pixels.
private void positionAt(int x, int y) {
moveTo(x - Math.round(mHotspotX), y - Math.round(mHotspotY));
}

// Returns the x coordinate of the position that the handle appears to be pointing to relative
// to the handles "parent" view.
private int getAdjustedPositionX() {
Expand All @@ -219,6 +297,16 @@ private int getAdjustedPositionY() {
return mPositionY + Math.round(mHotspotY);
}

private boolean isScrollInProgress() {
final PopupTouchHandleDrawableDelegate delegate = mDelegate.get();
if (delegate == null) {
hide();
return false;
}

return delegate.isScrollInProgress();
}

@CalledByNative
private void show() {
if (mContainer.isShowing()) return;
Expand All @@ -244,6 +332,7 @@ private void show() {

@CalledByNative
private void hide() {
mTemporarilyHidden = false;
mContainer.dismiss();
if (mParentPositionObserver != null) {
mParentPositionObserver.removeListener(mParentPositionListener);
Expand All @@ -269,19 +358,30 @@ private void setCenterOrientation() {

@CalledByNative
private void setOpacity(float alpha) {
if (mAlpha == alpha) return;
mAlpha = alpha;
if (mDrawable != null) mDrawable.setAlpha((int) (255 * mAlpha));
// Ignore opacity updates from the caller as they are not compatible
// with the custom fade animation.
}

@CalledByNative
private void setFocus(float x, float y) {
positionAt((int) x, (int) y);
private void setFocus(float focusX, float focusY) {
int x = (int) focusX - Math.round(mHotspotX);
int y = (int) focusY - Math.round(mHotspotY);
if (mPositionX == x && mPositionY == y) return;
mPositionX = x;
mPositionY = y;
if (isScrollInProgress()) {
temporarilyHide();
} else {
scheduleInvalidate();
}
}

@CalledByNative
private void setVisible(boolean visible) {
setVisibility(visible ? VISIBLE : INVISIBLE);
mVisible = visible;
int visibility = visible ? VISIBLE : INVISIBLE;
if (getVisibility() == visibility) return;
scheduleInvalidate();
}

@CalledByNative
Expand Down

0 comments on commit 4a73ea3

Please sign in to comment.