• 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 com.android.launcher3.Utilities.SINGLE_FRAME_MS;
20 
21 import android.content.Context;
22 import android.graphics.Rect;
23 import android.util.AttributeSet;
24 import android.view.MotionEvent;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.view.accessibility.AccessibilityEvent;
28 import android.widget.FrameLayout;
29 
30 import com.android.launcher3.AbstractFloatingView;
31 import com.android.launcher3.BaseActivity;
32 import com.android.launcher3.BaseDraggingActivity;
33 import com.android.launcher3.InsettableFrameLayout;
34 import com.android.launcher3.Utilities;
35 import com.android.launcher3.util.MultiValueAlpha;
36 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
37 import com.android.launcher3.util.TouchController;
38 
39 import java.util.ArrayList;
40 
41 /**
42  * A viewgroup with utility methods for drag-n-drop and touch interception
43  */
44 public abstract class BaseDragLayer<T extends BaseDraggingActivity> extends InsettableFrameLayout {
45 
46     protected final int[] mTmpXY = new int[2];
47     protected final Rect mHitRect = new Rect();
48 
49     protected final T mActivity;
50     private final MultiValueAlpha mMultiValueAlpha;
51 
52     protected TouchController[] mControllers;
53     protected TouchController mActiveController;
54     private TouchCompleteListener mTouchCompleteListener;
55 
BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount)56     public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
57         super(context, attrs);
58         mActivity = (T) BaseActivity.fromContext(context);
59         mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
60     }
61 
isEventOverView(View view, MotionEvent ev)62     public boolean isEventOverView(View view, MotionEvent ev) {
63         getDescendantRectRelativeToSelf(view, mHitRect);
64         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
65     }
66 
67     @Override
onInterceptTouchEvent(MotionEvent ev)68     public boolean onInterceptTouchEvent(MotionEvent ev) {
69         int action = ev.getAction();
70 
71         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
72             if (mTouchCompleteListener != null) {
73                 mTouchCompleteListener.onTouchComplete();
74             }
75             mTouchCompleteListener = null;
76         } else if (action == MotionEvent.ACTION_DOWN) {
77             mActivity.finishAutoCancelActionMode();
78         }
79         return findActiveController(ev);
80     }
81 
findActiveController(MotionEvent ev)82     protected boolean findActiveController(MotionEvent ev) {
83         mActiveController = null;
84 
85         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
86         if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
87             mActiveController = topView;
88             return true;
89         }
90 
91         for (TouchController controller : mControllers) {
92             if (controller.onControllerInterceptTouchEvent(ev)) {
93                 mActiveController = controller;
94                 return true;
95             }
96         }
97         return false;
98     }
99 
100     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)101     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
102         // Shortcuts can appear above folder
103         View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
104                 AbstractFloatingView.TYPE_ACCESSIBLE);
105         if (topView != null) {
106             if (child == topView) {
107                 return super.onRequestSendAccessibilityEvent(child, event);
108             }
109             // Skip propagating onRequestSendAccessibilityEvent for all other children
110             // which are not topView
111             return false;
112         }
113         return super.onRequestSendAccessibilityEvent(child, event);
114     }
115 
116     @Override
addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)117     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
118         View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
119                 AbstractFloatingView.TYPE_ACCESSIBLE);
120         if (topView != null) {
121             // Only add the top view as a child for accessibility when it is open
122             addAccessibleChildToList(topView, childrenForAccessibility);
123         } else {
124             super.addChildrenForAccessibility(childrenForAccessibility);
125         }
126     }
127 
addAccessibleChildToList(View child, ArrayList<View> outList)128     protected void addAccessibleChildToList(View child, ArrayList<View> outList) {
129         if (child.isImportantForAccessibility()) {
130             outList.add(child);
131         } else {
132             child.addChildrenForAccessibility(outList);
133         }
134     }
135 
136     @Override
onViewRemoved(View child)137     public void onViewRemoved(View child) {
138         super.onViewRemoved(child);
139         if (child instanceof AbstractFloatingView) {
140             // Handles the case where the view is removed without being properly closed.
141             // This can happen if something goes wrong during a state change/transition.
142             postDelayed(() -> {
143                 AbstractFloatingView floatingView = (AbstractFloatingView) child;
144                 if (floatingView.isOpen()) {
145                     floatingView.close(false);
146                 }
147             }, SINGLE_FRAME_MS);
148         }
149     }
150 
151     @Override
onTouchEvent(MotionEvent ev)152     public boolean onTouchEvent(MotionEvent ev) {
153         int action = ev.getAction();
154         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
155             if (mTouchCompleteListener != null) {
156                 mTouchCompleteListener.onTouchComplete();
157             }
158             mTouchCompleteListener = null;
159         }
160 
161         if (mActiveController != null) {
162             return mActiveController.onControllerTouchEvent(ev);
163         } else {
164             // In case no child view handled the touch event, we may not get onIntercept anymore
165             return findActiveController(ev);
166         }
167     }
168 
169     /**
170      * Determine the rect of the descendant in this DragLayer's coordinates
171      *
172      * @param descendant The descendant whose coordinates we want to find.
173      * @param r The rect into which to place the results.
174      * @return The factor by which this descendant is scaled relative to this DragLayer.
175      */
getDescendantRectRelativeToSelf(View descendant, Rect r)176     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
177         mTmpXY[0] = 0;
178         mTmpXY[1] = 0;
179         float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
180 
181         r.set(mTmpXY[0], mTmpXY[1],
182                 (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
183                 (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
184         return scale;
185     }
186 
getLocationInDragLayer(View child, int[] loc)187     public float getLocationInDragLayer(View child, int[] loc) {
188         loc[0] = 0;
189         loc[1] = 0;
190         return getDescendantCoordRelativeToSelf(child, loc);
191     }
192 
getDescendantCoordRelativeToSelf(View descendant, int[] coord)193     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
194         return getDescendantCoordRelativeToSelf(descendant, coord, false);
195     }
196 
197     /**
198      * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
199      * coordinates.
200      *
201      * @param descendant The descendant to which the passed coordinate is relative.
202      * @param coord The coordinate that we want mapped.
203      * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
204      *          sometimes this is relevant as in a child's coordinates within the root descendant.
205      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
206      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
207      *         assumption fails, we will need to return a pair of scale factors.
208      */
getDescendantCoordRelativeToSelf(View descendant, int[] coord, boolean includeRootScroll)209     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
210             boolean includeRootScroll) {
211         return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
212                 coord, includeRootScroll);
213     }
214 
215     /**
216      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
217      */
mapCoordInSelfToDescendant(View descendant, int[] coord)218     public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
219         Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
220     }
221 
getViewRectRelativeToSelf(View v, Rect r)222     public void getViewRectRelativeToSelf(View v, Rect r) {
223         int[] loc = new int[2];
224         getLocationInWindow(loc);
225         int x = loc[0];
226         int y = loc[1];
227 
228         v.getLocationInWindow(loc);
229         int vX = loc[0];
230         int vY = loc[1];
231 
232         int left = vX - x;
233         int top = vY - y;
234         r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
235     }
236 
237     @Override
dispatchUnhandledMove(View focused, int direction)238     public boolean dispatchUnhandledMove(View focused, int direction) {
239         // Consume the unhandled move if a container is open, to avoid switching pages underneath.
240         return AbstractFloatingView.getTopOpenView(mActivity) != null;
241     }
242 
243     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)244     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
245         View topView = AbstractFloatingView.getTopOpenView(mActivity);
246         if (topView != null) {
247             return topView.requestFocus(direction, previouslyFocusedRect);
248         } else {
249             return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
250         }
251     }
252 
253     @Override
addFocusables(ArrayList<View> views, int direction, int focusableMode)254     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
255         View topView = AbstractFloatingView.getTopOpenView(mActivity);
256         if (topView != null) {
257             topView.addFocusables(views, direction);
258         } else {
259             super.addFocusables(views, direction, focusableMode);
260         }
261     }
262 
setTouchCompleteListener(TouchCompleteListener listener)263     public void setTouchCompleteListener(TouchCompleteListener listener) {
264         mTouchCompleteListener = listener;
265     }
266 
267     public interface TouchCompleteListener {
onTouchComplete()268         void onTouchComplete();
269     }
270 
271     @Override
generateLayoutParams(AttributeSet attrs)272     public LayoutParams generateLayoutParams(AttributeSet attrs) {
273         return new LayoutParams(getContext(), attrs);
274     }
275 
276     @Override
generateDefaultLayoutParams()277     protected LayoutParams generateDefaultLayoutParams() {
278         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
279     }
280 
281     // Override to allow type-checking of LayoutParams.
282     @Override
checkLayoutParams(ViewGroup.LayoutParams p)283     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
284         return p instanceof LayoutParams;
285     }
286 
287     @Override
generateLayoutParams(ViewGroup.LayoutParams p)288     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
289         return new LayoutParams(p);
290     }
291 
getAlphaProperty(int index)292     public AlphaProperty getAlphaProperty(int index) {
293         return mMultiValueAlpha.getProperty(index);
294     }
295 
296     public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
297         public int x, y;
298         public boolean customPosition = false;
299 
LayoutParams(Context c, AttributeSet attrs)300         public LayoutParams(Context c, AttributeSet attrs) {
301             super(c, attrs);
302         }
303 
LayoutParams(int width, int height)304         public LayoutParams(int width, int height) {
305             super(width, height);
306         }
307 
LayoutParams(ViewGroup.LayoutParams lp)308         public LayoutParams(ViewGroup.LayoutParams lp) {
309             super(lp);
310         }
311 
setWidth(int width)312         public void setWidth(int width) {
313             this.width = width;
314         }
315 
getWidth()316         public int getWidth() {
317             return width;
318         }
319 
setHeight(int height)320         public void setHeight(int height) {
321             this.height = height;
322         }
323 
getHeight()324         public int getHeight() {
325             return height;
326         }
327 
setX(int x)328         public void setX(int x) {
329             this.x = x;
330         }
331 
getX()332         public int getX() {
333             return x;
334         }
335 
setY(int y)336         public void setY(int y) {
337             this.y = y;
338         }
339 
getY()340         public int getY() {
341             return y;
342         }
343     }
344 
onLayout(boolean changed, int l, int t, int r, int b)345     protected void onLayout(boolean changed, int l, int t, int r, int b) {
346         super.onLayout(changed, l, t, r, b);
347         int count = getChildCount();
348         for (int i = 0; i < count; i++) {
349             View child = getChildAt(i);
350             final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
351             if (flp instanceof LayoutParams) {
352                 final LayoutParams lp = (LayoutParams) flp;
353                 if (lp.customPosition) {
354                     child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
355                 }
356             }
357         }
358     }
359 }
360