• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.views;
18 
19 import static android.view.MotionEvent.ACTION_CANCEL;
20 import static android.view.MotionEvent.ACTION_DOWN;
21 import static android.view.MotionEvent.ACTION_UP;
22 
23 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
24 import static com.android.launcher3.Utilities.shouldDisableGestures;
25 
26 import android.annotation.TargetApi;
27 import android.content.Context;
28 import android.graphics.Insets;
29 import android.graphics.Rect;
30 import android.graphics.RectF;
31 import android.os.Build;
32 import android.util.AttributeSet;
33 import android.util.Log;
34 import android.util.Property;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import android.view.ViewDebug;
38 import android.view.ViewGroup;
39 import android.view.WindowInsets;
40 import android.view.accessibility.AccessibilityEvent;
41 import android.widget.FrameLayout;
42 
43 import com.android.launcher3.AbstractFloatingView;
44 import com.android.launcher3.InsettableFrameLayout;
45 import com.android.launcher3.Utilities;
46 import com.android.launcher3.testing.TestProtocol;
47 import com.android.launcher3.util.MultiValueAlpha;
48 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
49 import com.android.launcher3.util.TouchController;
50 
51 import java.io.PrintWriter;
52 import java.util.ArrayList;
53 
54 /**
55  * A viewgroup with utility methods for drag-n-drop and touch interception
56  */
57 public abstract class BaseDragLayer<T extends Context & ActivityContext>
58         extends InsettableFrameLayout {
59 
60     public static final Property<LayoutParams, Integer> LAYOUT_X =
61             new Property<LayoutParams, Integer>(Integer.TYPE, "x") {
62                 @Override
63                 public Integer get(LayoutParams lp) {
64                     return lp.x;
65                 }
66 
67                 @Override
68                 public void set(LayoutParams lp, Integer x) {
69                     lp.x = x;
70                 }
71             };
72 
73     public static final Property<LayoutParams, Integer> LAYOUT_Y =
74             new Property<LayoutParams, Integer>(Integer.TYPE, "y") {
75                 @Override
76                 public Integer get(LayoutParams lp) {
77                     return lp.y;
78                 }
79 
80                 @Override
81                 public void set(LayoutParams lp, Integer y) {
82                     lp.y = y;
83                 }
84             };
85 
86     // Touch is being dispatched through the normal view dispatch system
87     private static final int TOUCH_DISPATCHING_VIEW = 1 << 0;
88     // Touch is being dispatched through the normal view dispatch system, and started at the
89     // system gesture region
90     private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1;
91     // Touch is being dispatched through a proxy from InputMonitor
92     private static final int TOUCH_DISPATCHING_PROXY = 1 << 2;
93 
94     protected final float[] mTmpXY = new float[2];
95     protected final float[] mTmpRectPoints = new float[4];
96     protected final Rect mHitRect = new Rect();
97 
98     @ViewDebug.ExportedProperty(category = "launcher")
99     private final RectF mSystemGestureRegion = new RectF();
100     private int mTouchDispatchState = 0;
101 
102     protected final T mActivity;
103     private final MultiValueAlpha mMultiValueAlpha;
104 
105     // All the touch controllers for the view
106     protected TouchController[] mControllers;
107     // Touch controller which is currently active for the normal view dispatch
108     protected TouchController mActiveController;
109     // Touch controller which is being used for the proxy events
110     protected TouchController mProxyTouchController;
111 
112     private TouchCompleteListener mTouchCompleteListener;
113 
BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount)114     public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
115         super(context, attrs);
116         mActivity = (T) ActivityContext.lookupContext(context);
117         mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
118     }
119 
120     /**
121      * Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer.
122      */
isEventOverView(View view, MotionEvent ev)123     public boolean isEventOverView(View view, MotionEvent ev) {
124         getDescendantRectRelativeToSelf(view, mHitRect);
125         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
126     }
127 
128     /**
129      * Given a motion event in evView's coordinates, return whether the event is within another
130      * view's bounds.
131      */
isEventOverView(View view, MotionEvent ev, View evView)132     public boolean isEventOverView(View view, MotionEvent ev, View evView) {
133         int[] xy = new int[] {(int) ev.getX(), (int) ev.getY()};
134         getDescendantCoordRelativeToSelf(evView, xy);
135         getDescendantRectRelativeToSelf(view, mHitRect);
136         return mHitRect.contains(xy[0], xy[1]);
137     }
138 
139     @Override
onInterceptTouchEvent(MotionEvent ev)140     public boolean onInterceptTouchEvent(MotionEvent ev) {
141         int action = ev.getAction();
142 
143         if (action == ACTION_UP || action == ACTION_CANCEL) {
144             if (mTouchCompleteListener != null) {
145                 mTouchCompleteListener.onTouchComplete();
146             }
147             mTouchCompleteListener = null;
148         } else if (action == MotionEvent.ACTION_DOWN) {
149             mActivity.finishAutoCancelActionMode();
150         }
151         return findActiveController(ev);
152     }
153 
findControllerToHandleTouch(MotionEvent ev)154     private TouchController findControllerToHandleTouch(MotionEvent ev) {
155         if (shouldDisableGestures(ev)) return null;
156 
157         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
158         if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
159             return topView;
160         }
161 
162         for (TouchController controller : mControllers) {
163             if (controller.onControllerInterceptTouchEvent(ev)) {
164                 return controller;
165             }
166         }
167         return null;
168     }
169 
findActiveController(MotionEvent ev)170     protected boolean findActiveController(MotionEvent ev) {
171         mActiveController = null;
172         if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) {
173             // Only look for controllers if we are not dispatching from gesture area and proxy is
174             // not active
175             mActiveController = findControllerToHandleTouch(ev);
176 
177             if (mActiveController != null) return true;
178         }
179         return false;
180     }
181 
182     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)183     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
184         // Shortcuts can appear above folder
185         View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
186                 AbstractFloatingView.TYPE_ACCESSIBLE);
187         if (topView != null) {
188             if (child == topView) {
189                 return super.onRequestSendAccessibilityEvent(child, event);
190             }
191             // Skip propagating onRequestSendAccessibilityEvent for all other children
192             // which are not topView
193             return false;
194         }
195         return super.onRequestSendAccessibilityEvent(child, event);
196     }
197 
198     @Override
addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)199     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
200         View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
201                 AbstractFloatingView.TYPE_ACCESSIBLE);
202         if (topView != null) {
203             // Only add the top view as a child for accessibility when it is open
204             addAccessibleChildToList(topView, childrenForAccessibility);
205         } else {
206             super.addChildrenForAccessibility(childrenForAccessibility);
207         }
208     }
209 
addAccessibleChildToList(View child, ArrayList<View> outList)210     protected void addAccessibleChildToList(View child, ArrayList<View> outList) {
211         if (child.isImportantForAccessibility()) {
212             outList.add(child);
213         } else {
214             child.addChildrenForAccessibility(outList);
215         }
216     }
217 
218     @Override
onViewRemoved(View child)219     public void onViewRemoved(View child) {
220         super.onViewRemoved(child);
221         if (child instanceof AbstractFloatingView) {
222             // Handles the case where the view is removed without being properly closed.
223             // This can happen if something goes wrong during a state change/transition.
224             AbstractFloatingView floatingView = (AbstractFloatingView) child;
225             if (floatingView.isOpen()) {
226                 postDelayed(() -> floatingView.close(false), SINGLE_FRAME_MS);
227             }
228         }
229     }
230 
231     @Override
onTouchEvent(MotionEvent ev)232     public boolean onTouchEvent(MotionEvent ev) {
233         if (TestProtocol.sDebugTracing) {
234             android.util.Log.d(TestProtocol.NO_DRAG_TAG,
235                     "onTouchEvent " + ev);
236         }
237         int action = ev.getAction();
238         if (action == ACTION_UP || action == ACTION_CANCEL) {
239             if (mTouchCompleteListener != null) {
240                 mTouchCompleteListener.onTouchComplete();
241             }
242             mTouchCompleteListener = null;
243         }
244 
245         if (mActiveController != null) {
246             if (TestProtocol.sDebugTracing) {
247                 android.util.Log.d(TestProtocol.NO_DRAG_TAG,
248                         "onTouchEvent 1");
249             }
250             return mActiveController.onControllerTouchEvent(ev);
251         } else {
252             // In case no child view handled the touch event, we may not get onIntercept anymore
253             return findActiveController(ev);
254         }
255     }
256 
257     @Override
dispatchTouchEvent(MotionEvent ev)258     public boolean dispatchTouchEvent(MotionEvent ev) {
259         if (TestProtocol.sDebugTracing) {
260             Log.d(TestProtocol.NO_START_TAG, "BaseDragLayer.dispatchTouchEvent " + ev);
261         }
262         switch (ev.getAction()) {
263             case ACTION_DOWN: {
264                 float x = ev.getX();
265                 float y = ev.getY();
266                 mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
267 
268                 if ((y < mSystemGestureRegion.top
269                         || x < mSystemGestureRegion.left
270                         || x > (getWidth() - mSystemGestureRegion.right)
271                         || y > (getHeight() - mSystemGestureRegion.bottom))) {
272                     mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
273                 } else {
274                     mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
275                 }
276                 break;
277             }
278             case ACTION_CANCEL:
279             case ACTION_UP:
280                 mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
281                 mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
282                 break;
283         }
284         super.dispatchTouchEvent(ev);
285 
286         // We want to get all events so that mTouchDispatchSource is maintained properly
287         return true;
288     }
289 
290     /**
291      * Called before we are about to receive proxy events.
292      *
293      * @return false if we can't handle proxy at this time
294      */
prepareProxyEventStarting()295     public boolean prepareProxyEventStarting() {
296         mProxyTouchController = null;
297         if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) {
298             // We are already dispatching using view system and have an active controller, we can't
299             // handle another controller.
300 
301             // This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just
302             // to be safe
303             mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
304             return false;
305         }
306 
307         mTouchDispatchState |= TOUCH_DISPATCHING_PROXY;
308         return true;
309     }
310 
311     /**
312      * Proxies the touch events to the gesture handlers
313      */
proxyTouchEvent(MotionEvent ev)314     public boolean proxyTouchEvent(MotionEvent ev) {
315         boolean handled;
316         if (mProxyTouchController != null) {
317             handled = mProxyTouchController.onControllerTouchEvent(ev);
318         } else {
319             mProxyTouchController = findControllerToHandleTouch(ev);
320             handled = mProxyTouchController != null;
321         }
322         int action = ev.getAction();
323         if (action == ACTION_UP || action == ACTION_CANCEL) {
324             mProxyTouchController = null;
325             mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
326         }
327         return handled;
328     }
329 
330     /**
331      * Determine the rect of the descendant in this DragLayer's coordinates
332      *
333      * @param descendant The descendant whose coordinates we want to find.
334      * @param r The rect into which to place the results.
335      * @return The factor by which this descendant is scaled relative to this DragLayer.
336      */
getDescendantRectRelativeToSelf(View descendant, Rect r)337     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
338         mTmpRectPoints[0] = 0;
339         mTmpRectPoints[1] = 0;
340         mTmpRectPoints[2] = descendant.getWidth();
341         mTmpRectPoints[3] = descendant.getHeight();
342         float s = getDescendantCoordRelativeToSelf(descendant, mTmpRectPoints);
343         r.left = Math.round(Math.min(mTmpRectPoints[0], mTmpRectPoints[2]));
344         r.top = Math.round(Math.min(mTmpRectPoints[1], mTmpRectPoints[3]));
345         r.right = Math.round(Math.max(mTmpRectPoints[0], mTmpRectPoints[2]));
346         r.bottom = Math.round(Math.max(mTmpRectPoints[1], mTmpRectPoints[3]));
347         return s;
348     }
349 
getLocationInDragLayer(View child, int[] loc)350     public float getLocationInDragLayer(View child, int[] loc) {
351         loc[0] = 0;
352         loc[1] = 0;
353         return getDescendantCoordRelativeToSelf(child, loc);
354     }
355 
getDescendantCoordRelativeToSelf(View descendant, int[] coord)356     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
357         mTmpXY[0] = coord[0];
358         mTmpXY[1] = coord[1];
359         float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
360         Utilities.roundArray(mTmpXY, coord);
361         return scale;
362     }
363 
getDescendantCoordRelativeToSelf(View descendant, float[] coord)364     public float getDescendantCoordRelativeToSelf(View descendant, float[] coord) {
365         return getDescendantCoordRelativeToSelf(descendant, coord, false);
366     }
367 
368     /**
369      * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
370      * coordinates.
371      *
372      * @param descendant The descendant to which the passed coordinate is relative.
373      * @param coord The coordinate that we want mapped.
374      * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
375      *          sometimes this is relevant as in a child's coordinates within the root descendant.
376      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
377      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
378      *         assumption fails, we will need to return a pair of scale factors.
379      */
getDescendantCoordRelativeToSelf(View descendant, float[] coord, boolean includeRootScroll)380     public float getDescendantCoordRelativeToSelf(View descendant, float[] coord,
381             boolean includeRootScroll) {
382         return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
383                 coord, includeRootScroll);
384     }
385 
386     /**
387      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}.
388      */
mapCoordInSelfToDescendant(View descendant, float[] coord)389     public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
390         Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
391     }
392 
393     /**
394      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
395      */
mapCoordInSelfToDescendant(View descendant, int[] coord)396     public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
397         mTmpXY[0] = coord[0];
398         mTmpXY[1] = coord[1];
399         Utilities.mapCoordInSelfToDescendant(descendant, this, mTmpXY);
400         Utilities.roundArray(mTmpXY, coord);
401     }
402 
getViewRectRelativeToSelf(View v, Rect r)403     public void getViewRectRelativeToSelf(View v, Rect r) {
404         int[] loc = new int[2];
405         getLocationInWindow(loc);
406         int x = loc[0];
407         int y = loc[1];
408 
409         v.getLocationInWindow(loc);
410         int vX = loc[0];
411         int vY = loc[1];
412 
413         int left = vX - x;
414         int top = vY - y;
415         r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
416     }
417 
418     @Override
dispatchUnhandledMove(View focused, int direction)419     public boolean dispatchUnhandledMove(View focused, int direction) {
420         // Consume the unhandled move if a container is open, to avoid switching pages underneath.
421         return AbstractFloatingView.getTopOpenView(mActivity) != null;
422     }
423 
424     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)425     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
426         View topView = AbstractFloatingView.getTopOpenView(mActivity);
427         if (topView != null) {
428             return topView.requestFocus(direction, previouslyFocusedRect);
429         } else {
430             return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
431         }
432     }
433 
434     @Override
addFocusables(ArrayList<View> views, int direction, int focusableMode)435     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
436         View topView = AbstractFloatingView.getTopOpenView(mActivity);
437         if (topView != null) {
438             topView.addFocusables(views, direction);
439         } else {
440             super.addFocusables(views, direction, focusableMode);
441         }
442     }
443 
setTouchCompleteListener(TouchCompleteListener listener)444     public void setTouchCompleteListener(TouchCompleteListener listener) {
445         mTouchCompleteListener = listener;
446     }
447 
448     public interface TouchCompleteListener {
onTouchComplete()449         void onTouchComplete();
450     }
451 
452     @Override
generateLayoutParams(AttributeSet attrs)453     public LayoutParams generateLayoutParams(AttributeSet attrs) {
454         return new LayoutParams(getContext(), attrs);
455     }
456 
457     @Override
generateDefaultLayoutParams()458     protected LayoutParams generateDefaultLayoutParams() {
459         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
460     }
461 
462     // Override to allow type-checking of LayoutParams.
463     @Override
checkLayoutParams(ViewGroup.LayoutParams p)464     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
465         return p instanceof LayoutParams;
466     }
467 
468     @Override
generateLayoutParams(ViewGroup.LayoutParams p)469     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
470         return new LayoutParams(p);
471     }
472 
getAlphaProperty(int index)473     public AlphaProperty getAlphaProperty(int index) {
474         return mMultiValueAlpha.getProperty(index);
475     }
476 
dump(String prefix, PrintWriter writer)477     public void dump(String prefix, PrintWriter writer) {
478         writer.println(prefix + "DragLayer");
479         if (mActiveController != null) {
480             writer.println(prefix + "\tactiveController: " + mActiveController);
481             mActiveController.dump(prefix + "\t", writer);
482         }
483         writer.println(prefix + "\tdragLayerAlpha : " + mMultiValueAlpha );
484     }
485 
486     public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
487         public int x, y;
488         public boolean customPosition = false;
489 
LayoutParams(Context c, AttributeSet attrs)490         public LayoutParams(Context c, AttributeSet attrs) {
491             super(c, attrs);
492         }
493 
LayoutParams(int width, int height)494         public LayoutParams(int width, int height) {
495             super(width, height);
496         }
497 
LayoutParams(ViewGroup.LayoutParams lp)498         public LayoutParams(ViewGroup.LayoutParams lp) {
499             super(lp);
500         }
501     }
502 
onLayout(boolean changed, int l, int t, int r, int b)503     protected void onLayout(boolean changed, int l, int t, int r, int b) {
504         super.onLayout(changed, l, t, r, b);
505         int count = getChildCount();
506         for (int i = 0; i < count; i++) {
507             View child = getChildAt(i);
508             final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
509             if (flp instanceof LayoutParams) {
510                 final LayoutParams lp = (LayoutParams) flp;
511                 if (lp.customPosition) {
512                     child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
513                 }
514             }
515         }
516     }
517 
518     @Override
519     @TargetApi(Build.VERSION_CODES.Q)
dispatchApplyWindowInsets(WindowInsets insets)520     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
521         if (Utilities.ATLEAST_Q) {
522             Insets gestureInsets = insets.getMandatorySystemGestureInsets();
523             mSystemGestureRegion.set(gestureInsets.left, gestureInsets.top,
524                     gestureInsets.right, gestureInsets.bottom);
525         }
526         return super.dispatchApplyWindowInsets(insets);
527     }
528 }
529