Handling touch events on the caption.
We need a more sophisticated touch handling to support overlaying the caption. The touch events need to be routed in following order: close/maximize buttons, application content, caption dragging. Bug: 25486369 Change-Id: I9d4e971fb055c217c0bd83f0490fb42a5c22e93b
This commit is contained in:
@@ -224,7 +224,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
* NOTE: If you change the flags below make sure to reflect the changes
|
||||
* the DisplayList class
|
||||
*/
|
||||
|
||||
|
||||
// When set, ViewGroup invalidates only the child's rectangle
|
||||
// Set by default
|
||||
static final int FLAG_CLIP_CHILDREN = 0x1;
|
||||
@@ -269,7 +269,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
/**
|
||||
* When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
|
||||
* to get the index of the child to draw for that iteration.
|
||||
*
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
|
||||
@@ -1327,7 +1327,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
children[i].dispatchConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -2214,7 +2214,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
final float y = ev.getY(actionIndex);
|
||||
// Find a child that can receive the event.
|
||||
// Scan children from front to back.
|
||||
final ArrayList<View> preorderedList = buildOrderedChildList();
|
||||
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
|
||||
final boolean customOrder = preorderedList == null
|
||||
&& isChildrenDrawingOrderEnabled();
|
||||
final View[] children = mChildren;
|
||||
@@ -2346,6 +2346,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
return handled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide custom ordering of views in which the touch will be dispatched.
|
||||
*
|
||||
* This is called within a tight loop, so you are not allowed to allocate objects, including
|
||||
* the return array. Instead, you should return a pre-allocated list that will be cleared
|
||||
* after the dispatch is finished.
|
||||
* @hide
|
||||
*/
|
||||
public ArrayList<View> buildTouchDispatchChildList() {
|
||||
return buildOrderedChildList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the child which has accessibility focus.
|
||||
*
|
||||
@@ -2787,7 +2799,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
* @see #FOCUS_BEFORE_DESCENDANTS
|
||||
* @see #FOCUS_AFTER_DESCENDANTS
|
||||
* @see #FOCUS_BLOCK_DESCENDANTS
|
||||
* @see #onRequestFocusInDescendants(int, android.graphics.Rect)
|
||||
* @see #onRequestFocusInDescendants(int, android.graphics.Rect)
|
||||
*/
|
||||
@Override
|
||||
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
|
||||
@@ -4104,7 +4116,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
/**
|
||||
* <p>Adds a child view. If no layout parameters are already set on the child, the
|
||||
* default parameters for this ViewGroup are set on the child.</p>
|
||||
*
|
||||
*
|
||||
* <p><strong>Note:</strong> do not invoke this method from
|
||||
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
|
||||
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
|
||||
@@ -4120,7 +4132,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
/**
|
||||
* Adds a child view. If no layout parameters are already set on the child, the
|
||||
* default parameters for this ViewGroup are set on the child.
|
||||
*
|
||||
*
|
||||
* <p><strong>Note:</strong> do not invoke this method from
|
||||
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
|
||||
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
|
||||
@@ -4560,7 +4572,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
*
|
||||
* <p><strong>Note:</strong> do not invoke this method from
|
||||
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
|
||||
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
|
||||
@@ -4579,7 +4591,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
* <p><strong>Note:</strong> do not invoke this method from
|
||||
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
|
||||
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
|
||||
*
|
||||
*
|
||||
* @param view the view to remove from the group
|
||||
*/
|
||||
public void removeViewInLayout(View view) {
|
||||
@@ -4607,7 +4619,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
* <p><strong>Note:</strong> do not invoke this method from
|
||||
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
|
||||
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
|
||||
*
|
||||
*
|
||||
* @param index the position in the group of the view to remove
|
||||
*/
|
||||
public void removeViewAt(int index) {
|
||||
@@ -4796,7 +4808,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
/**
|
||||
* Call this method to remove all child views from the
|
||||
* ViewGroup.
|
||||
*
|
||||
*
|
||||
* <p><strong>Note:</strong> do not invoke this method from
|
||||
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
|
||||
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
|
||||
|
||||
@@ -19,18 +19,24 @@ package com.android.internal.widget;
|
||||
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.os.RemoteException;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.Window;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.policy.PhoneWindow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This class represents the special screen elements to control a window on freeform
|
||||
* environment.
|
||||
@@ -38,8 +44,8 @@ import com.android.internal.policy.PhoneWindow;
|
||||
* <ul>
|
||||
* <li>The caption, containing the system buttons like maximize, close and such as well as
|
||||
* allowing the user to drag the window around.</li>
|
||||
* After creating the view, the function
|
||||
* {@link #setPhoneWindow} needs to be called to make
|
||||
* </ul>
|
||||
* After creating the view, the function {@link #setPhoneWindow} needs to be called to make
|
||||
* the connection to it's owning PhoneWindow.
|
||||
* Note: At this time the application can change various attributes of the DecorView which
|
||||
* will break things (in settle/unexpected ways):
|
||||
@@ -48,9 +54,29 @@ import com.android.internal.policy.PhoneWindow;
|
||||
* <li>setSurfaceFormat</li>
|
||||
* <li>..</li>
|
||||
* </ul>
|
||||
*
|
||||
* Although this ViewGroup has only two direct sub-Views, its behavior is more complex due to
|
||||
* overlaying caption on the content and drawing.
|
||||
*
|
||||
* First, no matter where the content View gets added, it will always be the first child and the
|
||||
* caption will be the second. This way the caption will always be drawn on top of the content when
|
||||
* overlaying is enabled.
|
||||
*
|
||||
* Second, the touch dispatch is customized to handle overlaying. This is what happens when touch
|
||||
* is dispatched on the caption area while overlaying it on content:
|
||||
* <ul>
|
||||
* <li>DecorCaptionView.onInterceptTouchEvent() will try intercepting the touch events if the
|
||||
* down action is performed on top close or maximize buttons; the reason for that is we want these
|
||||
* buttons to always work.</li>
|
||||
* <li>The content View will receive the touch event. Mind that content is actually underneath the
|
||||
* caption, so we need to introduce our own dispatch ordering. We achieve this by overriding
|
||||
* {@link #buildTouchDispatchChildList()}.</li>
|
||||
* <li>If the touch event is not consumed by the content View, it will go to the caption View
|
||||
* and the dragging logic will be executed.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class DecorCaptionView extends ViewGroup
|
||||
implements View.OnClickListener, View.OnTouchListener {
|
||||
public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
|
||||
GestureDetector.OnGestureListener {
|
||||
private final static String TAG = "DecorCaptionView";
|
||||
private PhoneWindow mOwner = null;
|
||||
private boolean mShow = false;
|
||||
@@ -65,17 +91,42 @@ public class DecorCaptionView extends ViewGroup
|
||||
|
||||
private View mCaption;
|
||||
private View mContent;
|
||||
private View mMaximize;
|
||||
private View mClose;
|
||||
|
||||
// Fields for detecting drag events.
|
||||
private int mTouchDownX;
|
||||
private int mTouchDownY;
|
||||
private boolean mCheckForDragging;
|
||||
private int mDragSlop;
|
||||
|
||||
// Fields for detecting and intercepting click events on close/maximize.
|
||||
private ArrayList<View> mTouchDispatchList = new ArrayList<>(2);
|
||||
// We use the gesture detector to detect clicks on close/maximize buttons and to be consistent
|
||||
// with existing click detection.
|
||||
private GestureDetector mGestureDetector;
|
||||
private final Rect mCloseRect = new Rect();
|
||||
private final Rect mMaximizeRect = new Rect();
|
||||
private View mClickTarget;
|
||||
|
||||
public DecorCaptionView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public DecorCaptionView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mDragSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
mGestureDetector = new GestureDetector(context, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,13 +139,47 @@ public class DecorCaptionView extends ViewGroup
|
||||
mOwner = owner;
|
||||
mShow = show;
|
||||
mOverlayWithAppContent = owner.getOverlayDecorCaption();
|
||||
if (mOverlayWithAppContent) {
|
||||
// The caption is covering the content, so we make its background transparent to make
|
||||
// the content visible.
|
||||
mCaption.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
updateCaptionVisibility();
|
||||
// By changing the outline provider to BOUNDS, the window can remove its
|
||||
// background without removing the shadow.
|
||||
mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
|
||||
mMaximize = findViewById(R.id.maximize_window);
|
||||
mClose = findViewById(R.id.close_window);
|
||||
}
|
||||
|
||||
findViewById(R.id.maximize_window).setOnClickListener(this);
|
||||
findViewById(R.id.close_window).setOnClickListener(this);
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
// If the user starts touch on the maximize/close buttons, we immediately intercept, so
|
||||
// that these buttons are always clickable.
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
if (mMaximizeRect.contains(x, y)) {
|
||||
mClickTarget = mMaximize;
|
||||
}
|
||||
if (mCloseRect.contains(x, y)) {
|
||||
mClickTarget = mClose;
|
||||
}
|
||||
}
|
||||
return mClickTarget != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mClickTarget != null) {
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
final int action = event.getAction();
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
mClickTarget = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,25 +187,31 @@ public class DecorCaptionView extends ViewGroup
|
||||
// Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
|
||||
// the old input device events get cancelled first. So no need to remember the kind of
|
||||
// input device we are listening to.
|
||||
final int x = (int) e.getX();
|
||||
final int y = (int) e.getY();
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (!mShow) {
|
||||
// When there is no caption we should not react to anything.
|
||||
return false;
|
||||
}
|
||||
// A drag action is started if we aren't dragging already and the starting event is
|
||||
// either a left mouse button or any other input device.
|
||||
if (!mDragging &&
|
||||
(e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
|
||||
(e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
|
||||
mDragging = true;
|
||||
mLeftMouseButtonReleased = false;
|
||||
startMovingTask(e.getRawX(), e.getRawY());
|
||||
// Checking for a drag action is started if we aren't dragging already and the
|
||||
// starting event is either a left mouse button or any other input device.
|
||||
if (((e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
|
||||
(e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0))) {
|
||||
mCheckForDragging = true;
|
||||
mTouchDownX = x;
|
||||
mTouchDownY = y;
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (mDragging && !mLeftMouseButtonReleased) {
|
||||
if (!mDragging && mCheckForDragging && passedSlop(x, y)) {
|
||||
mCheckForDragging = false;
|
||||
mDragging = true;
|
||||
mLeftMouseButtonReleased = false;
|
||||
startMovingTask(e.getRawX(), e.getRawY());
|
||||
} else if (mDragging && !mLeftMouseButtonReleased) {
|
||||
if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
|
||||
(e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
|
||||
// There is no separate mouse button up call and if the user mixes mouse
|
||||
@@ -138,9 +229,25 @@ public class DecorCaptionView extends ViewGroup
|
||||
}
|
||||
// Abort the ongoing dragging.
|
||||
mDragging = false;
|
||||
return true;
|
||||
return !mCheckForDragging;
|
||||
}
|
||||
return mDragging;
|
||||
return mDragging || mCheckForDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<View> buildTouchDispatchChildList() {
|
||||
mTouchDispatchList.ensureCapacity(3);
|
||||
if (mCaption != null) {
|
||||
mTouchDispatchList.add(mCaption);
|
||||
}
|
||||
if (mContent != null) {
|
||||
mTouchDispatchList.add(mContent);
|
||||
}
|
||||
return mTouchDispatchList;
|
||||
}
|
||||
|
||||
private boolean passedSlop(int x, int y) {
|
||||
return Math.abs(x - mTouchDownX) > mDragSlop || Math.abs(y - mTouchDownY) > mDragSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,15 +259,6 @@ public class DecorCaptionView extends ViewGroup
|
||||
updateCaptionVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view.getId() == R.id.maximize_window) {
|
||||
maximizeWindow();
|
||||
} else if (view.getId() == R.id.close_window) {
|
||||
mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
if (!(params instanceof MarginLayoutParams)) {
|
||||
@@ -205,8 +303,12 @@ public class DecorCaptionView extends ViewGroup
|
||||
if (mCaption.getVisibility() != View.GONE) {
|
||||
mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
|
||||
captionHeight = mCaption.getBottom() - mCaption.getTop();
|
||||
mMaximize.getHitRect(mMaximizeRect);
|
||||
mClose.getHitRect(mCloseRect);
|
||||
} else {
|
||||
captionHeight = 0;
|
||||
mMaximizeRect.setEmpty();
|
||||
mCloseRect.setEmpty();
|
||||
}
|
||||
|
||||
if (mContent != null) {
|
||||
@@ -291,4 +393,39 @@ public class DecorCaptionView extends ViewGroup
|
||||
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
||||
return p instanceof MarginLayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
if (mClickTarget == mMaximize) {
|
||||
maximizeWindow();
|
||||
} else if (mClickTarget == mClose) {
|
||||
mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user