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