Loading core/java/android/view/ViewGroup.java +23 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading core/java/com/android/internal/widget/DecorCaptionView.java +164 −27 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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): Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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; } /** Loading @@ -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)) { Loading Loading @@ -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) { Loading Loading @@ -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; } } Loading
core/java/android/view/ViewGroup.java +23 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading
core/java/com/android/internal/widget/DecorCaptionView.java +164 −27 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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): Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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; } /** Loading @@ -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)) { Loading Loading @@ -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) { Loading Loading @@ -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; } }