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