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