• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright (C) 2008 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.launcher3.dragndrop;
19 
20 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.TimeInterpolator;
25 import android.animation.ValueAnimator;
26 import android.animation.ValueAnimator.AnimatorUpdateListener;
27 import android.content.Context;
28 import android.content.res.Resources;
29 import android.graphics.Canvas;
30 import android.graphics.Rect;
31 import android.util.AttributeSet;
32 import android.view.KeyEvent;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.accessibility.AccessibilityEvent;
36 import android.view.accessibility.AccessibilityManager;
37 import android.view.animation.Interpolator;
38 import android.widget.TextView;
39 
40 import com.android.launcher3.AbstractFloatingView;
41 import com.android.launcher3.CellLayout;
42 import com.android.launcher3.DropTargetBar;
43 import com.android.launcher3.Launcher;
44 import com.android.launcher3.R;
45 import com.android.launcher3.ShortcutAndWidgetContainer;
46 import com.android.launcher3.Workspace;
47 import com.android.launcher3.anim.Interpolators;
48 import com.android.launcher3.folder.Folder;
49 import com.android.launcher3.folder.FolderIcon;
50 import com.android.launcher3.graphics.ViewScrim;
51 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
52 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
53 import com.android.launcher3.uioverrides.UiFactory;
54 import com.android.launcher3.util.Thunk;
55 import com.android.launcher3.views.BaseDragLayer;
56 
57 import java.util.ArrayList;
58 
59 /**
60  * A ViewGroup that coordinates dragging across its descendants
61  */
62 public class DragLayer extends BaseDragLayer<Launcher> {
63 
64     public static final int ALPHA_INDEX_OVERLAY = 0;
65     public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
66     public static final int ALPHA_INDEX_TRANSITIONS = 2;
67     public static final int ALPHA_INDEX_SWIPE_UP = 3;
68     private static final int ALPHA_CHANNEL_COUNT = 4;
69 
70     public static final int ANIMATION_END_DISAPPEAR = 0;
71     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
72 
73     @Thunk DragController mDragController;
74 
75     // Variables relating to animation of views after drop
76     private ValueAnimator mDropAnim = null;
77     private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5;
78     @Thunk DragView mDropView = null;
79     @Thunk int mAnchorViewInitialScrollX = 0;
80     @Thunk View mAnchorView = null;
81 
82     private boolean mHoverPointClosesFolder = false;
83 
84     private int mTopViewIndex;
85     private int mChildCountOnLastUpdate = -1;
86 
87     // Related to adjacent page hints
88     private final ViewGroupFocusHelper mFocusIndicatorHelper;
89     private final WorkspaceAndHotseatScrim mScrim;
90 
91     /**
92      * Used to create a new DragLayer from XML.
93      *
94      * @param context The application's context.
95      * @param attrs The attributes set containing the Workspace's customization values.
96      */
DragLayer(Context context, AttributeSet attrs)97     public DragLayer(Context context, AttributeSet attrs) {
98         super(context, attrs, ALPHA_CHANNEL_COUNT);
99 
100         // Disable multitouch across the workspace/all apps/customize tray
101         setMotionEventSplittingEnabled(false);
102         setChildrenDrawingOrderEnabled(true);
103 
104         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
105         mScrim = new WorkspaceAndHotseatScrim(this);
106     }
107 
setup(DragController dragController, Workspace workspace)108     public void setup(DragController dragController, Workspace workspace) {
109         mDragController = dragController;
110         mScrim.setWorkspace(workspace);
111         recreateControllers();
112     }
113 
recreateControllers()114     public void recreateControllers() {
115         mControllers = UiFactory.createTouchControllers(mActivity);
116     }
117 
getFocusIndicatorHelper()118     public ViewGroupFocusHelper getFocusIndicatorHelper() {
119         return mFocusIndicatorHelper;
120     }
121 
122     @Override
dispatchKeyEvent(KeyEvent event)123     public boolean dispatchKeyEvent(KeyEvent event) {
124         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
125     }
126 
127     @Override
drawChild(Canvas canvas, View child, long drawingTime)128     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
129         ViewScrim scrim = ViewScrim.get(child);
130         if (scrim != null) {
131             scrim.draw(canvas, getWidth(), getHeight());
132         }
133         return super.drawChild(canvas, child, drawingTime);
134     }
135 
136     @Override
findActiveController(MotionEvent ev)137     protected boolean findActiveController(MotionEvent ev) {
138         if (mActivity.getStateManager().getState().disableInteraction) {
139             // You Shall Not Pass!!!
140             mActiveController = null;
141             return true;
142         }
143         return super.findActiveController(ev);
144     }
145 
isEventOverAccessibleDropTargetBar(MotionEvent ev)146     private boolean isEventOverAccessibleDropTargetBar(MotionEvent ev) {
147         return isInAccessibleDrag() && isEventOverView(mActivity.getDropTargetBar(), ev);
148     }
149 
150     @Override
onInterceptHoverEvent(MotionEvent ev)151     public boolean onInterceptHoverEvent(MotionEvent ev) {
152         if (mActivity == null || mActivity.getWorkspace() == null) {
153             return false;
154         }
155         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
156         if (!(topView instanceof Folder)) {
157             return false;
158         } else {
159             AccessibilityManager accessibilityManager = (AccessibilityManager)
160                     getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
161             if (accessibilityManager.isTouchExplorationEnabled()) {
162                 Folder currentFolder = (Folder) topView;
163                 final int action = ev.getAction();
164                 boolean isOverFolderOrSearchBar;
165                 switch (action) {
166                     case MotionEvent.ACTION_HOVER_ENTER:
167                         isOverFolderOrSearchBar = isEventOverView(topView, ev) ||
168                                 isEventOverAccessibleDropTargetBar(ev);
169                         if (!isOverFolderOrSearchBar) {
170                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
171                             mHoverPointClosesFolder = true;
172                             return true;
173                         }
174                         mHoverPointClosesFolder = false;
175                         break;
176                     case MotionEvent.ACTION_HOVER_MOVE:
177                         isOverFolderOrSearchBar = isEventOverView(topView, ev) ||
178                                 isEventOverAccessibleDropTargetBar(ev);
179                         if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
180                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
181                             mHoverPointClosesFolder = true;
182                             return true;
183                         } else if (!isOverFolderOrSearchBar) {
184                             return true;
185                         }
186                         mHoverPointClosesFolder = false;
187                 }
188             }
189         }
190         return false;
191     }
192 
sendTapOutsideFolderAccessibilityEvent(boolean isEditingName)193     private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
194         int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
195         sendCustomAccessibilityEvent(
196                 this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId));
197     }
198 
199     @Override
onHoverEvent(MotionEvent ev)200     public boolean onHoverEvent(MotionEvent ev) {
201         // If we've received this, we've already done the necessary handling
202         // in onInterceptHoverEvent. Return true to consume the event.
203         return false;
204     }
205 
206 
isInAccessibleDrag()207     private boolean isInAccessibleDrag() {
208         return mActivity.getAccessibilityDelegate().isInAccessibleDrag();
209     }
210 
211     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)212     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
213         if (isInAccessibleDrag() && child instanceof DropTargetBar) {
214             return true;
215         }
216         return super.onRequestSendAccessibilityEvent(child, event);
217     }
218 
219     @Override
addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)220     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
221         View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
222                 AbstractFloatingView.TYPE_ACCESSIBLE);
223         if (topView != null) {
224             addAccessibleChildToList(topView, childrenForAccessibility);
225             if (isInAccessibleDrag()) {
226                 addAccessibleChildToList(mActivity.getDropTargetBar(), childrenForAccessibility);
227             }
228         } else {
229             super.addChildrenForAccessibility(childrenForAccessibility);
230         }
231     }
232 
233     @Override
dispatchUnhandledMove(View focused, int direction)234     public boolean dispatchUnhandledMove(View focused, int direction) {
235         return super.dispatchUnhandledMove(focused, direction)
236                 || mDragController.dispatchUnhandledMove(focused, direction);
237     }
238 
239     @Override
dispatchTouchEvent(MotionEvent ev)240     public boolean dispatchTouchEvent(MotionEvent ev) {
241         ev.offsetLocation(getTranslationX(), 0);
242         try {
243             return super.dispatchTouchEvent(ev);
244         } finally {
245             ev.offsetLocation(-getTranslationX(), 0);
246         }
247     }
248 
animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration)249     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
250             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
251             int duration) {
252         Rect r = new Rect();
253         getViewRectRelativeToSelf(dragView, r);
254         final int fromX = r.left;
255         final int fromY = r.top;
256 
257         animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
258                 onFinishRunnable, animationEndStyle, duration, null);
259     }
260 
animateViewIntoPosition(DragView dragView, final View child, View anchorView)261     public void animateViewIntoPosition(DragView dragView, final View child, View anchorView) {
262         animateViewIntoPosition(dragView, child, -1, anchorView);
263     }
264 
animateViewIntoPosition(DragView dragView, final View child, int duration, View anchorView)265     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
266             View anchorView) {
267         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
268         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
269         parentChildren.measureChild(child);
270 
271         Rect r = new Rect();
272         getViewRectRelativeToSelf(dragView, r);
273 
274         int coord[] = new int[2];
275         float childScale = child.getScaleX();
276         coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
277         coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
278 
279         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
280         // the correct coordinates (above) and use these to determine the final location
281         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
282         // We need to account for the scale of the child itself, as the above only accounts for
283         // for the scale in parents.
284         scale *= childScale;
285         int toX = coord[0];
286         int toY = coord[1];
287         float toScale = scale;
288         if (child instanceof TextView) {
289             TextView tv = (TextView) child;
290             // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
291             // the workspace may have smaller icon bounds).
292             toScale = scale / dragView.getIntrinsicIconScaleFactor();
293 
294             // The child may be scaled (always about the center of the view) so to account for it,
295             // we have to offset the position by the scaled size.  Once we do that, we can center
296             // the drag view about the scaled child view.
297             toY += Math.round(toScale * tv.getPaddingTop());
298             toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
299             if (dragView.getDragVisualizeOffset() != null) {
300                 toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
301             }
302 
303             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
304         } else if (child instanceof FolderIcon) {
305             // Account for holographic blur padding on the drag view
306             toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
307             toY -= scale * dragView.getBlurSizeOutline() / 2;
308             toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
309             // Center in the x coordinate about the target's drawable
310             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
311         } else {
312             toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
313             toX -= (Math.round(scale * (dragView.getMeasuredWidth()
314                     - child.getMeasuredWidth()))) / 2;
315         }
316 
317         final int fromX = r.left;
318         final int fromY = r.top;
319         child.setVisibility(INVISIBLE);
320         Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
321         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
322                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
323     }
324 
animateViewIntoPosition(final DragView view, final int fromX, final int fromY, final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, int animationEndStyle, int duration, View anchorView)325     public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
326             final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
327             float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
328             int animationEndStyle, int duration, View anchorView) {
329         Rect from = new Rect(fromX, fromY, fromX +
330                 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
331         Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
332         animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
333                 null, null, onCompleteRunnable, animationEndStyle, anchorView);
334     }
335 
336     /**
337      * This method animates a view at the end of a drag and drop animation.
338      *
339      * @param view The view to be animated. This view is drawn directly into DragLayer, and so
340      *        doesn't need to be a child of DragLayer.
341      * @param from The initial location of the view. Only the left and top parameters are used.
342      * @param to The final location of the view. Only the left and top parameters are used. This
343      *        location doesn't account for scaling, and so should be centered about the desired
344      *        final location (including scaling).
345      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
346      * @param finalScaleX The final scale of the view. The view is scaled about its center.
347      * @param finalScaleY The final scale of the view. The view is scaled about its center.
348      * @param duration The duration of the animation.
349      * @param motionInterpolator The interpolator to use for the location of the view.
350      * @param alphaInterpolator The interpolator to use for the alpha of the view.
351      * @param onCompleteRunnable Optional runnable to run on animation completion.
352      * @param animationEndStyle Whether or not to fade out the view once the animation completes.
353      *        {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
354      * @param anchorView If not null, this represents the view which the animated view stays
355      *        anchored to in case scrolling is currently taking place. Note: currently this is
356      *        only used for the X dimension for the case of the workspace.
357      */
animateView(final DragView view, final Rect from, final Rect to, final float finalAlpha, final float initScaleX, final float initScaleY, final float finalScaleX, final float finalScaleY, int duration, final Interpolator motionInterpolator, final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)358     public void animateView(final DragView view, final Rect from, final Rect to,
359             final float finalAlpha, final float initScaleX, final float initScaleY,
360             final float finalScaleX, final float finalScaleY, int duration,
361             final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
362             final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
363 
364         // Calculate the duration of the animation based on the object's distance
365         final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top);
366         final Resources res = getResources();
367         final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
368 
369         // If duration < 0, this is a cue to compute the duration based on the distance
370         if (duration < 0) {
371             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
372             if (dist < maxDist) {
373                 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
374             }
375             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
376         }
377 
378         // Fall back to cubic ease out interpolator for the animation if none is specified
379         TimeInterpolator interpolator = null;
380         if (alphaInterpolator == null || motionInterpolator == null) {
381             interpolator = mCubicEaseOutInterpolator;
382         }
383 
384         // Animate the view
385         final float initAlpha = view.getAlpha();
386         final float dropViewScale = view.getScaleX();
387         AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
388             @Override
389             public void onAnimationUpdate(ValueAnimator animation) {
390                 final float percent = (Float) animation.getAnimatedValue();
391                 final int width = view.getMeasuredWidth();
392                 final int height = view.getMeasuredHeight();
393 
394                 float alphaPercent = alphaInterpolator == null ? percent :
395                         alphaInterpolator.getInterpolation(percent);
396                 float motionPercent = motionInterpolator == null ? percent :
397                         motionInterpolator.getInterpolation(percent);
398 
399                 float initialScaleX = initScaleX * dropViewScale;
400                 float initialScaleY = initScaleY * dropViewScale;
401                 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
402                 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
403                 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
404 
405                 float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
406                 float fromTop = from.top + (initialScaleY - 1f) * height / 2;
407 
408                 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
409                 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
410 
411                 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
412                     (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
413 
414                 int xPos = x - mDropView.getScrollX() + anchorAdjust;
415                 int yPos = y - mDropView.getScrollY();
416 
417                 mDropView.setTranslationX(xPos);
418                 mDropView.setTranslationY(yPos);
419                 mDropView.setScaleX(scaleX);
420                 mDropView.setScaleY(scaleY);
421                 mDropView.setAlpha(alpha);
422             }
423         };
424         animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
425                 anchorView);
426     }
427 
animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, TimeInterpolator interpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)428     public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
429             TimeInterpolator interpolator, final Runnable onCompleteRunnable,
430             final int animationEndStyle, View anchorView) {
431         // Clean up the previous animations
432         if (mDropAnim != null) mDropAnim.cancel();
433 
434         // Show the drop view if it was previously hidden
435         mDropView = view;
436         mDropView.cancelAnimation();
437         mDropView.requestLayout();
438 
439         // Set the anchor view if the page is scrolling
440         if (anchorView != null) {
441             mAnchorViewInitialScrollX = anchorView.getScrollX();
442         }
443         mAnchorView = anchorView;
444 
445         // Create and start the animation
446         mDropAnim = new ValueAnimator();
447         mDropAnim.setInterpolator(interpolator);
448         mDropAnim.setDuration(duration);
449         mDropAnim.setFloatValues(0f, 1f);
450         mDropAnim.addUpdateListener(updateCb);
451         mDropAnim.addListener(new AnimatorListenerAdapter() {
452             public void onAnimationEnd(Animator animation) {
453                 if (onCompleteRunnable != null) {
454                     onCompleteRunnable.run();
455                 }
456                 switch (animationEndStyle) {
457                 case ANIMATION_END_DISAPPEAR:
458                     clearAnimatedView();
459                     break;
460                 case ANIMATION_END_REMAIN_VISIBLE:
461                     break;
462                 }
463                 mDropAnim = null;
464             }
465         });
466         mDropAnim.start();
467     }
468 
clearAnimatedView()469     public void clearAnimatedView() {
470         if (mDropAnim != null) {
471             mDropAnim.cancel();
472         }
473         mDropAnim = null;
474         if (mDropView != null) {
475             mDragController.onDeferredEndDrag(mDropView);
476         }
477         mDropView = null;
478         invalidate();
479     }
480 
getAnimatedView()481     public View getAnimatedView() {
482         return mDropView;
483     }
484 
485     @Override
onViewAdded(View child)486     public void onViewAdded(View child) {
487         super.onViewAdded(child);
488         updateChildIndices();
489         UiFactory.onLauncherStateOrFocusChanged(mActivity);
490     }
491 
492     @Override
onViewRemoved(View child)493     public void onViewRemoved(View child) {
494         super.onViewRemoved(child);
495         updateChildIndices();
496         UiFactory.onLauncherStateOrFocusChanged(mActivity);
497     }
498 
499     @Override
bringChildToFront(View child)500     public void bringChildToFront(View child) {
501         super.bringChildToFront(child);
502         updateChildIndices();
503     }
504 
updateChildIndices()505     private void updateChildIndices() {
506         mTopViewIndex = -1;
507         int childCount = getChildCount();
508         for (int i = 0; i < childCount; i++) {
509             if (getChildAt(i) instanceof DragView) {
510                 mTopViewIndex = i;
511             }
512         }
513         mChildCountOnLastUpdate = childCount;
514     }
515 
516     @Override
getChildDrawingOrder(int childCount, int i)517     protected int getChildDrawingOrder(int childCount, int i) {
518         if (mChildCountOnLastUpdate != childCount) {
519             // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed.
520             // Pre-18, the child was not added / removed by the time of those callbacks. We need to
521             // force update our representation of things here to avoid crashing on pre-18 devices
522             // in certain instances.
523             updateChildIndices();
524         }
525 
526         // i represents the current draw iteration
527         if (mTopViewIndex == -1) {
528             // in general we do nothing
529             return i;
530         } else if (i == childCount - 1) {
531             // if we have a top index, we return it when drawing last item (highest z-order)
532             return mTopViewIndex;
533         } else if (i < mTopViewIndex) {
534             return i;
535         } else {
536             // for indexes greater than the top index, we fetch one item above to shift for the
537             // displacement of the top index
538             return i + 1;
539         }
540     }
541 
542     @Override
dispatchDraw(Canvas canvas)543     protected void dispatchDraw(Canvas canvas) {
544         // Draw the background below children.
545         mScrim.draw(canvas);
546         mFocusIndicatorHelper.draw(canvas);
547         super.dispatchDraw(canvas);
548     }
549 
550     @Override
onSizeChanged(int w, int h, int oldw, int oldh)551     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
552         super.onSizeChanged(w, h, oldw, oldh);
553         mScrim.setSize(w, h);
554     }
555 
556     @Override
setInsets(Rect insets)557     public void setInsets(Rect insets) {
558         super.setInsets(insets);
559         mScrim.onInsetsChanged(insets);
560     }
561 
getScrim()562     public WorkspaceAndHotseatScrim getScrim() {
563         return mScrim;
564     }
565 }
566