mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-28 15:56:49 +00:00
Previously it was using OtherActivityInputConsumer, which got things in a pretty weird state (e.g. most recent app would appear in the center as if it was the active app when you started Quick Switching, etc.). By default (toggleable by a feature flag), OverviewWithoutFocusInputConsumer is used because Assistant doesn't seem to respect the CLOSE_SYSTEM_DIALOGS broadcast, at least in half-shelf mode. In this case, the Home intent is sent on swipe up, or you can dismiss it with the back gesture or by tapping above the half shelf. The new feature flag ASSISTANT_GIVES_LAUNCHER_FOCUS routes touches through OverviewInputConsumer. As opposed to OverviewWithoutFocusInputConsumer, this allows Quick Step to work while Assistant is running. Additional logic is added to dismiss the Assistant when appropriate. Note that the dismissal happens atomically, so it's not completely fluid with the other animations. As mentioned above, this is disabled by default because Assistant doesn't currently respect CLOSE_SYSTEM_DIALOGS. Demo with the flag enabled (and Assistant respecting CLOSE_SYSTEM_DIALOGS): https://drive.google.com/open?id=1W5jGpn_TEC-KjrYwQtaBT3pzxG_5tC4W Bug: 139661510 Change-Id: I261653118aff289b329ec2a7ca6e52f100f7835a Tested: Manually used quick switch after invoking Assistant from home screen.
517 lines
19 KiB
Java
517 lines
19 KiB
Java
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3.views;
|
|
|
|
import static android.view.MotionEvent.ACTION_CANCEL;
|
|
import static android.view.MotionEvent.ACTION_DOWN;
|
|
import static android.view.MotionEvent.ACTION_UP;
|
|
|
|
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.content.Context;
|
|
import android.graphics.Insets;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.os.Build;
|
|
import android.util.AttributeSet;
|
|
import android.util.Log;
|
|
import android.util.Property;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewDebug;
|
|
import android.view.ViewGroup;
|
|
import android.view.WindowInsets;
|
|
import android.view.accessibility.AccessibilityEvent;
|
|
import android.widget.FrameLayout;
|
|
|
|
import com.android.launcher3.AbstractFloatingView;
|
|
import com.android.launcher3.InsettableFrameLayout;
|
|
import com.android.launcher3.Utilities;
|
|
import com.android.launcher3.testing.TestProtocol;
|
|
import com.android.launcher3.util.MultiValueAlpha;
|
|
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
|
|
import com.android.launcher3.util.TouchController;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.ArrayList;
|
|
|
|
/**
|
|
* A viewgroup with utility methods for drag-n-drop and touch interception
|
|
*/
|
|
public abstract class BaseDragLayer<T extends Context & ActivityContext>
|
|
extends InsettableFrameLayout {
|
|
|
|
public static final Property<LayoutParams, Integer> LAYOUT_X =
|
|
new Property<LayoutParams, Integer>(Integer.TYPE, "x") {
|
|
@Override
|
|
public Integer get(LayoutParams lp) {
|
|
return lp.x;
|
|
}
|
|
|
|
@Override
|
|
public void set(LayoutParams lp, Integer x) {
|
|
lp.x = x;
|
|
}
|
|
};
|
|
|
|
public static final Property<LayoutParams, Integer> LAYOUT_Y =
|
|
new Property<LayoutParams, Integer>(Integer.TYPE, "y") {
|
|
@Override
|
|
public Integer get(LayoutParams lp) {
|
|
return lp.y;
|
|
}
|
|
|
|
@Override
|
|
public void set(LayoutParams lp, Integer y) {
|
|
lp.y = y;
|
|
}
|
|
};
|
|
|
|
// Touch is being dispatched through the normal view dispatch system
|
|
private static final int TOUCH_DISPATCHING_VIEW = 1 << 0;
|
|
// Touch is being dispatched through the normal view dispatch system, and started at the
|
|
// system gesture region
|
|
private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1;
|
|
// Touch is being dispatched through a proxy from InputMonitor
|
|
private static final int TOUCH_DISPATCHING_PROXY = 1 << 2;
|
|
|
|
protected final float[] mTmpXY = new float[2];
|
|
protected final float[] mTmpRectPoints = new float[4];
|
|
protected final Rect mHitRect = new Rect();
|
|
|
|
@ViewDebug.ExportedProperty(category = "launcher")
|
|
private final RectF mSystemGestureRegion = new RectF();
|
|
private int mTouchDispatchState = 0;
|
|
|
|
protected final T mActivity;
|
|
private final MultiValueAlpha mMultiValueAlpha;
|
|
|
|
// All the touch controllers for the view
|
|
protected TouchController[] mControllers;
|
|
// Touch controller which is currently active for the normal view dispatch
|
|
protected TouchController mActiveController;
|
|
// Touch controller which is being used for the proxy events
|
|
protected TouchController mProxyTouchController;
|
|
|
|
private TouchCompleteListener mTouchCompleteListener;
|
|
|
|
public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
|
|
super(context, attrs);
|
|
mActivity = (T) ActivityContext.lookupContext(context);
|
|
mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
|
|
}
|
|
|
|
/**
|
|
* Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer.
|
|
*/
|
|
public boolean isEventOverView(View view, MotionEvent ev) {
|
|
getDescendantRectRelativeToSelf(view, mHitRect);
|
|
return mHitRect.contains((int) ev.getX(), (int) ev.getY());
|
|
}
|
|
|
|
/**
|
|
* Given a motion event in evView's coordinates, return whether the event is within another
|
|
* view's bounds.
|
|
*/
|
|
public boolean isEventOverView(View view, MotionEvent ev, View evView) {
|
|
int[] xy = new int[] {(int) ev.getX(), (int) ev.getY()};
|
|
getDescendantCoordRelativeToSelf(evView, xy);
|
|
getDescendantRectRelativeToSelf(view, mHitRect);
|
|
return mHitRect.contains(xy[0], xy[1]);
|
|
}
|
|
|
|
@Override
|
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
int action = ev.getAction();
|
|
|
|
if (action == ACTION_UP || action == ACTION_CANCEL) {
|
|
if (mTouchCompleteListener != null) {
|
|
mTouchCompleteListener.onTouchComplete();
|
|
}
|
|
mTouchCompleteListener = null;
|
|
} else if (action == MotionEvent.ACTION_DOWN) {
|
|
mActivity.finishAutoCancelActionMode();
|
|
}
|
|
return findActiveController(ev);
|
|
}
|
|
|
|
private TouchController findControllerToHandleTouch(MotionEvent ev) {
|
|
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
|
|
return topView;
|
|
}
|
|
|
|
for (TouchController controller : mControllers) {
|
|
if (controller.onControllerInterceptTouchEvent(ev)) {
|
|
return controller;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected boolean findActiveController(MotionEvent ev) {
|
|
mActiveController = null;
|
|
if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) {
|
|
// Only look for controllers if we are not dispatching from gesture area and proxy is
|
|
// not active
|
|
mActiveController = findControllerToHandleTouch(ev);
|
|
}
|
|
return mActiveController != null;
|
|
}
|
|
|
|
@Override
|
|
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
|
|
// Shortcuts can appear above folder
|
|
View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
|
|
AbstractFloatingView.TYPE_ACCESSIBLE);
|
|
if (topView != null) {
|
|
if (child == topView) {
|
|
return super.onRequestSendAccessibilityEvent(child, event);
|
|
}
|
|
// Skip propagating onRequestSendAccessibilityEvent for all other children
|
|
// which are not topView
|
|
return false;
|
|
}
|
|
return super.onRequestSendAccessibilityEvent(child, event);
|
|
}
|
|
|
|
@Override
|
|
public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
|
|
View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
|
|
AbstractFloatingView.TYPE_ACCESSIBLE);
|
|
if (topView != null) {
|
|
// Only add the top view as a child for accessibility when it is open
|
|
addAccessibleChildToList(topView, childrenForAccessibility);
|
|
} else {
|
|
super.addChildrenForAccessibility(childrenForAccessibility);
|
|
}
|
|
}
|
|
|
|
protected void addAccessibleChildToList(View child, ArrayList<View> outList) {
|
|
if (child.isImportantForAccessibility()) {
|
|
outList.add(child);
|
|
} else {
|
|
child.addChildrenForAccessibility(outList);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onViewRemoved(View child) {
|
|
super.onViewRemoved(child);
|
|
if (child instanceof AbstractFloatingView) {
|
|
// Handles the case where the view is removed without being properly closed.
|
|
// This can happen if something goes wrong during a state change/transition.
|
|
AbstractFloatingView floatingView = (AbstractFloatingView) child;
|
|
if (floatingView.isOpen()) {
|
|
postDelayed(() -> floatingView.close(false), getSingleFrameMs(getContext()));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
int action = ev.getAction();
|
|
if (action == ACTION_UP || action == ACTION_CANCEL) {
|
|
if (mTouchCompleteListener != null) {
|
|
mTouchCompleteListener.onTouchComplete();
|
|
}
|
|
mTouchCompleteListener = null;
|
|
}
|
|
|
|
if (mActiveController != null) {
|
|
return mActiveController.onControllerTouchEvent(ev);
|
|
} else {
|
|
// In case no child view handled the touch event, we may not get onIntercept anymore
|
|
return findActiveController(ev);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
|
switch (ev.getAction()) {
|
|
case ACTION_DOWN: {
|
|
float x = ev.getX();
|
|
float y = ev.getY();
|
|
mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
|
|
|
|
if ((y < mSystemGestureRegion.top
|
|
|| x < mSystemGestureRegion.left
|
|
|| x > (getWidth() - mSystemGestureRegion.right)
|
|
|| y > (getHeight() - mSystemGestureRegion.bottom))) {
|
|
mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
|
|
} else {
|
|
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
|
|
}
|
|
break;
|
|
}
|
|
case ACTION_CANCEL:
|
|
case ACTION_UP:
|
|
if (TestProtocol.sDebugTracing) {
|
|
Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE,
|
|
"BaseDragLayer.ACTION_UP/CANCEL " + ev);
|
|
}
|
|
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
|
|
mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
|
|
break;
|
|
}
|
|
super.dispatchTouchEvent(ev);
|
|
|
|
// We want to get all events so that mTouchDispatchSource is maintained properly
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called before we are about to receive proxy events.
|
|
*
|
|
* @return false if we can't handle proxy at this time
|
|
*/
|
|
public boolean prepareProxyEventStarting() {
|
|
mProxyTouchController = null;
|
|
if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) {
|
|
// We are already dispatching using view system and have an active controller, we can't
|
|
// handle another controller.
|
|
|
|
// This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just
|
|
// to be safe
|
|
mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
|
|
return false;
|
|
}
|
|
|
|
mTouchDispatchState |= TOUCH_DISPATCHING_PROXY;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Proxies the touch events to the gesture handlers
|
|
*/
|
|
public boolean proxyTouchEvent(MotionEvent ev) {
|
|
boolean handled;
|
|
if (mProxyTouchController != null) {
|
|
handled = mProxyTouchController.onControllerTouchEvent(ev);
|
|
} else {
|
|
mProxyTouchController = findControllerToHandleTouch(ev);
|
|
handled = mProxyTouchController != null;
|
|
}
|
|
int action = ev.getAction();
|
|
if (action == ACTION_UP || action == ACTION_CANCEL) {
|
|
mProxyTouchController = null;
|
|
mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
/**
|
|
* Determine the rect of the descendant in this DragLayer's coordinates
|
|
*
|
|
* @param descendant The descendant whose coordinates we want to find.
|
|
* @param r The rect into which to place the results.
|
|
* @return The factor by which this descendant is scaled relative to this DragLayer.
|
|
*/
|
|
public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
|
|
mTmpRectPoints[0] = 0;
|
|
mTmpRectPoints[1] = 0;
|
|
mTmpRectPoints[2] = descendant.getWidth();
|
|
mTmpRectPoints[3] = descendant.getHeight();
|
|
float s = getDescendantCoordRelativeToSelf(descendant, mTmpRectPoints);
|
|
r.left = Math.round(Math.min(mTmpRectPoints[0], mTmpRectPoints[2]));
|
|
r.top = Math.round(Math.min(mTmpRectPoints[1], mTmpRectPoints[3]));
|
|
r.right = Math.round(Math.max(mTmpRectPoints[0], mTmpRectPoints[2]));
|
|
r.bottom = Math.round(Math.max(mTmpRectPoints[1], mTmpRectPoints[3]));
|
|
return s;
|
|
}
|
|
|
|
public float getLocationInDragLayer(View child, int[] loc) {
|
|
loc[0] = 0;
|
|
loc[1] = 0;
|
|
return getDescendantCoordRelativeToSelf(child, loc);
|
|
}
|
|
|
|
public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
|
|
mTmpXY[0] = coord[0];
|
|
mTmpXY[1] = coord[1];
|
|
float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
|
|
Utilities.roundArray(mTmpXY, coord);
|
|
return scale;
|
|
}
|
|
|
|
public float getDescendantCoordRelativeToSelf(View descendant, float[] coord) {
|
|
return getDescendantCoordRelativeToSelf(descendant, coord, false);
|
|
}
|
|
|
|
/**
|
|
* Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
|
|
* coordinates.
|
|
*
|
|
* @param descendant The descendant to which the passed coordinate is relative.
|
|
* @param coord The coordinate that we want mapped.
|
|
* @param includeRootScroll Whether or not to account for the scroll of the root descendant:
|
|
* sometimes this is relevant as in a child's coordinates within the root descendant.
|
|
* @return The factor by which this descendant is scaled relative to this DragLayer. Caution
|
|
* this scale factor is assumed to be equal in X and Y, and so if at any point this
|
|
* assumption fails, we will need to return a pair of scale factors.
|
|
*/
|
|
public float getDescendantCoordRelativeToSelf(View descendant, float[] coord,
|
|
boolean includeRootScroll) {
|
|
return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
|
|
coord, includeRootScroll);
|
|
}
|
|
|
|
/**
|
|
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}.
|
|
*/
|
|
public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
|
|
Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
|
|
}
|
|
|
|
/**
|
|
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
|
|
*/
|
|
public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
|
|
mTmpXY[0] = coord[0];
|
|
mTmpXY[1] = coord[1];
|
|
Utilities.mapCoordInSelfToDescendant(descendant, this, mTmpXY);
|
|
Utilities.roundArray(mTmpXY, coord);
|
|
}
|
|
|
|
public void getViewRectRelativeToSelf(View v, Rect r) {
|
|
int[] loc = new int[2];
|
|
getLocationInWindow(loc);
|
|
int x = loc[0];
|
|
int y = loc[1];
|
|
|
|
v.getLocationInWindow(loc);
|
|
int vX = loc[0];
|
|
int vY = loc[1];
|
|
|
|
int left = vX - x;
|
|
int top = vY - y;
|
|
r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchUnhandledMove(View focused, int direction) {
|
|
// Consume the unhandled move if a container is open, to avoid switching pages underneath.
|
|
return AbstractFloatingView.getTopOpenView(mActivity) != null;
|
|
}
|
|
|
|
@Override
|
|
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
|
|
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
if (topView != null) {
|
|
return topView.requestFocus(direction, previouslyFocusedRect);
|
|
} else {
|
|
return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
|
|
View topView = AbstractFloatingView.getTopOpenView(mActivity);
|
|
if (topView != null) {
|
|
topView.addFocusables(views, direction);
|
|
} else {
|
|
super.addFocusables(views, direction, focusableMode);
|
|
}
|
|
}
|
|
|
|
public void setTouchCompleteListener(TouchCompleteListener listener) {
|
|
mTouchCompleteListener = listener;
|
|
}
|
|
|
|
public interface TouchCompleteListener {
|
|
void onTouchComplete();
|
|
}
|
|
|
|
@Override
|
|
public LayoutParams generateLayoutParams(AttributeSet attrs) {
|
|
return new LayoutParams(getContext(), attrs);
|
|
}
|
|
|
|
@Override
|
|
protected LayoutParams generateDefaultLayoutParams() {
|
|
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
|
}
|
|
|
|
// Override to allow type-checking of LayoutParams.
|
|
@Override
|
|
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
|
return p instanceof LayoutParams;
|
|
}
|
|
|
|
@Override
|
|
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
|
|
return new LayoutParams(p);
|
|
}
|
|
|
|
public AlphaProperty getAlphaProperty(int index) {
|
|
return mMultiValueAlpha.getProperty(index);
|
|
}
|
|
|
|
public void dump(String prefix, PrintWriter writer) {
|
|
writer.println(prefix + "DragLayer:");
|
|
if (mActiveController != null) {
|
|
writer.println(prefix + "\tactiveController: " + mActiveController);
|
|
mActiveController.dump(prefix + "\t", writer);
|
|
}
|
|
writer.println(prefix + "\tdragLayerAlpha : " + mMultiValueAlpha );
|
|
}
|
|
|
|
public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
|
|
public int x, y;
|
|
public boolean customPosition = false;
|
|
|
|
public LayoutParams(Context c, AttributeSet attrs) {
|
|
super(c, attrs);
|
|
}
|
|
|
|
public LayoutParams(int width, int height) {
|
|
super(width, height);
|
|
}
|
|
|
|
public LayoutParams(ViewGroup.LayoutParams lp) {
|
|
super(lp);
|
|
}
|
|
}
|
|
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
super.onLayout(changed, l, t, r, b);
|
|
int count = getChildCount();
|
|
for (int i = 0; i < count; i++) {
|
|
View child = getChildAt(i);
|
|
final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
|
|
if (flp instanceof LayoutParams) {
|
|
final LayoutParams lp = (LayoutParams) flp;
|
|
if (lp.customPosition) {
|
|
child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@TargetApi(Build.VERSION_CODES.Q)
|
|
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
|
|
if (Utilities.ATLEAST_Q) {
|
|
Insets gestureInsets = insets.getMandatorySystemGestureInsets();
|
|
mSystemGestureRegion.set(gestureInsets.left, gestureInsets.top,
|
|
gestureInsets.right, gestureInsets.bottom);
|
|
}
|
|
return super.dispatchApplyWindowInsets(insets);
|
|
}
|
|
}
|