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 android.view.View.MeasureSpec.EXACTLY;
21 import static android.view.View.MeasureSpec.getMode;
22 import static android.view.View.MeasureSpec.getSize;
23 
24 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
25 
26 import android.animation.Animator;
27 import android.animation.AnimatorListenerAdapter;
28 import android.animation.TimeInterpolator;
29 import android.animation.ValueAnimator;
30 import android.animation.ValueAnimator.AnimatorUpdateListener;
31 import android.content.Context;
32 import android.content.res.Resources;
33 import android.graphics.Canvas;
34 import android.graphics.Rect;
35 import android.util.AttributeSet;
36 import android.view.Gravity;
37 import android.view.KeyEvent;
38 import android.view.MotionEvent;
39 import android.view.View;
40 import android.view.accessibility.AccessibilityEvent;
41 import android.view.accessibility.AccessibilityManager;
42 import android.view.animation.Interpolator;
43 import android.widget.FrameLayout;
44 import android.widget.TextView;
45 
46 import com.android.launcher3.AbstractFloatingView;
47 import com.android.launcher3.CellLayout;
48 import com.android.launcher3.DropTargetBar;
49 import com.android.launcher3.Launcher;
50 import com.android.launcher3.R;
51 import com.android.launcher3.ShortcutAndWidgetContainer;
52 import com.android.launcher3.Workspace;
53 import com.android.launcher3.anim.Interpolators;
54 import com.android.launcher3.folder.Folder;
55 import com.android.launcher3.folder.FolderIcon;
56 import com.android.launcher3.graphics.OverviewScrim;
57 import com.android.launcher3.graphics.RotationMode;
58 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
59 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
60 import com.android.launcher3.uioverrides.UiFactory;
61 import com.android.launcher3.util.Thunk;
62 import com.android.launcher3.views.BaseDragLayer;
63 import com.android.launcher3.views.Transposable;
64 
65 import java.util.ArrayList;
66 
67 /**
68  * A ViewGroup that coordinates dragging across its descendants
69  */
70 public class DragLayer extends BaseDragLayer<Launcher> {
71 
72     public static final int ALPHA_INDEX_OVERLAY = 0;
73     public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
74     public static final int ALPHA_INDEX_TRANSITIONS = 2;
75     private static final int ALPHA_CHANNEL_COUNT = 3;
76 
77     public static final int ANIMATION_END_DISAPPEAR = 0;
78     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
79 
80     @Thunk DragController mDragController;
81 
82     // Variables relating to animation of views after drop
83     private ValueAnimator mDropAnim = null;
84     private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5;
85     @Thunk DragView mDropView = null;
86     @Thunk int mAnchorViewInitialScrollX = 0;
87     @Thunk View mAnchorView = null;
88 
89     private boolean mHoverPointClosesFolder = false;
90 
91     private int mTopViewIndex;
92     private int mChildCountOnLastUpdate = -1;
93 
94     // Related to adjacent page hints
95     private final ViewGroupFocusHelper mFocusIndicatorHelper;
96     private final WorkspaceAndHotseatScrim mWorkspaceScrim;
97     private final OverviewScrim mOverviewScrim;
98 
99     /**
100      * Used to create a new DragLayer from XML.
101      *
102      * @param context The application's context.
103      * @param attrs The attributes set containing the Workspace's customization values.
104      */
DragLayer(Context context, AttributeSet attrs)105     public DragLayer(Context context, AttributeSet attrs) {
106         super(context, attrs, ALPHA_CHANNEL_COUNT);
107 
108         // Disable multitouch across the workspace/all apps/customize tray
109         setMotionEventSplittingEnabled(false);
110         setChildrenDrawingOrderEnabled(true);
111 
112         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
113         mWorkspaceScrim = new WorkspaceAndHotseatScrim(this);
114         mOverviewScrim = new OverviewScrim(this);
115     }
116 
setup(DragController dragController, Workspace workspace)117     public void setup(DragController dragController, Workspace workspace) {
118         mDragController = dragController;
119         mWorkspaceScrim.setWorkspace(workspace);
120         recreateControllers();
121     }
122 
recreateControllers()123     public void recreateControllers() {
124         mControllers = UiFactory.createTouchControllers(mActivity);
125     }
126 
getFocusIndicatorHelper()127     public ViewGroupFocusHelper getFocusIndicatorHelper() {
128         return mFocusIndicatorHelper;
129     }
130 
131     @Override
dispatchKeyEvent(KeyEvent event)132     public boolean dispatchKeyEvent(KeyEvent event) {
133         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
134     }
135 
isEventOverAccessibleDropTargetBar(MotionEvent ev)136     private boolean isEventOverAccessibleDropTargetBar(MotionEvent ev) {
137         return isInAccessibleDrag() && isEventOverView(mActivity.getDropTargetBar(), ev);
138     }
139 
140     @Override
onInterceptHoverEvent(MotionEvent ev)141     public boolean onInterceptHoverEvent(MotionEvent ev) {
142         if (mActivity == null || mActivity.getWorkspace() == null) {
143             return false;
144         }
145         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
146         if (!(topView instanceof Folder)) {
147             return false;
148         } else {
149             AccessibilityManager accessibilityManager = (AccessibilityManager)
150                     getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
151             if (accessibilityManager.isTouchExplorationEnabled()) {
152                 Folder currentFolder = (Folder) topView;
153                 final int action = ev.getAction();
154                 boolean isOverFolderOrSearchBar;
155                 switch (action) {
156                     case MotionEvent.ACTION_HOVER_ENTER:
157                         isOverFolderOrSearchBar = isEventOverView(topView, ev) ||
158                                 isEventOverAccessibleDropTargetBar(ev);
159                         if (!isOverFolderOrSearchBar) {
160                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
161                             mHoverPointClosesFolder = true;
162                             return true;
163                         }
164                         mHoverPointClosesFolder = false;
165                         break;
166                     case MotionEvent.ACTION_HOVER_MOVE:
167                         isOverFolderOrSearchBar = isEventOverView(topView, ev) ||
168                                 isEventOverAccessibleDropTargetBar(ev);
169                         if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
170                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
171                             mHoverPointClosesFolder = true;
172                             return true;
173                         } else if (!isOverFolderOrSearchBar) {
174                             return true;
175                         }
176                         mHoverPointClosesFolder = false;
177                 }
178             }
179         }
180         return false;
181     }
182 
sendTapOutsideFolderAccessibilityEvent(boolean isEditingName)183     private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
184         int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
185         sendCustomAccessibilityEvent(
186                 this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId));
187     }
188 
189     @Override
onHoverEvent(MotionEvent ev)190     public boolean onHoverEvent(MotionEvent ev) {
191         // If we've received this, we've already done the necessary handling
192         // in onInterceptHoverEvent. Return true to consume the event.
193         return false;
194     }
195 
196 
isInAccessibleDrag()197     private boolean isInAccessibleDrag() {
198         return mActivity.getAccessibilityDelegate().isInAccessibleDrag();
199     }
200 
201     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)202     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
203         if (isInAccessibleDrag() && child instanceof DropTargetBar) {
204             return true;
205         }
206         return super.onRequestSendAccessibilityEvent(child, event);
207     }
208 
209     @Override
addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)210     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
211         View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
212                 AbstractFloatingView.TYPE_ACCESSIBLE);
213         if (topView != null) {
214             addAccessibleChildToList(topView, childrenForAccessibility);
215             if (isInAccessibleDrag()) {
216                 addAccessibleChildToList(mActivity.getDropTargetBar(), childrenForAccessibility);
217             }
218         } else {
219             super.addChildrenForAccessibility(childrenForAccessibility);
220         }
221     }
222 
223     @Override
dispatchUnhandledMove(View focused, int direction)224     public boolean dispatchUnhandledMove(View focused, int direction) {
225         return super.dispatchUnhandledMove(focused, direction)
226                 || mDragController.dispatchUnhandledMove(focused, direction);
227     }
228 
229     @Override
dispatchTouchEvent(MotionEvent ev)230     public boolean dispatchTouchEvent(MotionEvent ev) {
231         ev.offsetLocation(getTranslationX(), 0);
232         try {
233             return super.dispatchTouchEvent(ev);
234         } finally {
235             ev.offsetLocation(-getTranslationX(), 0);
236         }
237     }
238 
animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration)239     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
240             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
241             int duration) {
242         Rect r = new Rect();
243         getViewRectRelativeToSelf(dragView, r);
244         final int fromX = r.left;
245         final int fromY = r.top;
246 
247         animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
248                 onFinishRunnable, animationEndStyle, duration, null);
249     }
250 
animateViewIntoPosition(DragView dragView, final View child, View anchorView)251     public void animateViewIntoPosition(DragView dragView, final View child, View anchorView) {
252         animateViewIntoPosition(dragView, child, -1, anchorView);
253     }
254 
animateViewIntoPosition(DragView dragView, final View child, int duration, View anchorView)255     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
256             View anchorView) {
257         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
258         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
259         parentChildren.measureChild(child);
260 
261         Rect r = new Rect();
262         getViewRectRelativeToSelf(dragView, r);
263 
264         float coord[] = new float[2];
265         float childScale = child.getScaleX();
266         coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
267         coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
268 
269         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
270         // the correct coordinates (above) and use these to determine the final location
271         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
272         // We need to account for the scale of the child itself, as the above only accounts for
273         // for the scale in parents.
274         scale *= childScale;
275         int toX = Math.round(coord[0]);
276         int toY = Math.round(coord[1]);
277         float toScale = scale;
278         if (child instanceof TextView) {
279             TextView tv = (TextView) child;
280             // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
281             // the workspace may have smaller icon bounds).
282             toScale = scale / dragView.getIntrinsicIconScaleFactor();
283 
284             // The child may be scaled (always about the center of the view) so to account for it,
285             // we have to offset the position by the scaled size.  Once we do that, we can center
286             // the drag view about the scaled child view.
287             // padding will remain constant (does not scale with size)
288             toY += tv.getPaddingTop();
289             toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
290             if (dragView.getDragVisualizeOffset() != null) {
291                 toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
292             }
293 
294             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
295         } else if (child instanceof FolderIcon) {
296             // Account for holographic blur padding on the drag view
297             toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
298             toY -= scale * dragView.getBlurSizeOutline() / 2;
299             toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
300             // Center in the x coordinate about the target's drawable
301             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
302         } else {
303             toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
304             toX -= (Math.round(scale * (dragView.getMeasuredWidth()
305                     - child.getMeasuredWidth()))) / 2;
306         }
307 
308         final int fromX = r.left;
309         final int fromY = r.top;
310         child.setVisibility(INVISIBLE);
311         Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
312         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
313                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
314     }
315 
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)316     public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
317             final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
318             float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
319             int animationEndStyle, int duration, View anchorView) {
320         Rect from = new Rect(fromX, fromY, fromX +
321                 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
322         Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
323         animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
324                 null, null, onCompleteRunnable, animationEndStyle, anchorView);
325     }
326 
327     /**
328      * This method animates a view at the end of a drag and drop animation.
329      *
330      * @param view The view to be animated. This view is drawn directly into DragLayer, and so
331      *        doesn't need to be a child of DragLayer.
332      * @param from The initial location of the view. Only the left and top parameters are used.
333      * @param to The final location of the view. Only the left and top parameters are used. This
334      *        location doesn't account for scaling, and so should be centered about the desired
335      *        final location (including scaling).
336      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
337      * @param finalScaleX The final scale of the view. The view is scaled about its center.
338      * @param finalScaleY The final scale of the view. The view is scaled about its center.
339      * @param duration The duration of the animation.
340      * @param motionInterpolator The interpolator to use for the location of the view.
341      * @param alphaInterpolator The interpolator to use for the alpha of the view.
342      * @param onCompleteRunnable Optional runnable to run on animation completion.
343      * @param animationEndStyle Whether or not to fade out the view once the animation completes.
344      *        {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
345      * @param anchorView If not null, this represents the view which the animated view stays
346      *        anchored to in case scrolling is currently taking place. Note: currently this is
347      *        only used for the X dimension for the case of the workspace.
348      */
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)349     public void animateView(final DragView view, final Rect from, final Rect to,
350             final float finalAlpha, final float initScaleX, final float initScaleY,
351             final float finalScaleX, final float finalScaleY, int duration,
352             final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
353             final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
354 
355         // Calculate the duration of the animation based on the object's distance
356         final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top);
357         final Resources res = getResources();
358         final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
359 
360         // If duration < 0, this is a cue to compute the duration based on the distance
361         if (duration < 0) {
362             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
363             if (dist < maxDist) {
364                 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
365             }
366             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
367         }
368 
369         // Fall back to cubic ease out interpolator for the animation if none is specified
370         TimeInterpolator interpolator = null;
371         if (alphaInterpolator == null || motionInterpolator == null) {
372             interpolator = mCubicEaseOutInterpolator;
373         }
374 
375         // Animate the view
376         final float initAlpha = view.getAlpha();
377         final float dropViewScale = view.getScaleX();
378         AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
379             @Override
380             public void onAnimationUpdate(ValueAnimator animation) {
381                 final float percent = (Float) animation.getAnimatedValue();
382                 final int width = view.getMeasuredWidth();
383                 final int height = view.getMeasuredHeight();
384 
385                 float alphaPercent = alphaInterpolator == null ? percent :
386                         alphaInterpolator.getInterpolation(percent);
387                 float motionPercent = motionInterpolator == null ? percent :
388                         motionInterpolator.getInterpolation(percent);
389 
390                 float initialScaleX = initScaleX * dropViewScale;
391                 float initialScaleY = initScaleY * dropViewScale;
392                 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
393                 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
394                 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
395 
396                 float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
397                 float fromTop = from.top + (initialScaleY - 1f) * height / 2;
398 
399                 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
400                 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
401 
402                 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
403                     (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
404 
405                 int xPos = x - mDropView.getScrollX() + anchorAdjust;
406                 int yPos = y - mDropView.getScrollY();
407 
408                 mDropView.setTranslationX(xPos);
409                 mDropView.setTranslationY(yPos);
410                 mDropView.setScaleX(scaleX);
411                 mDropView.setScaleY(scaleY);
412                 mDropView.setAlpha(alpha);
413             }
414         };
415         animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
416                 anchorView);
417     }
418 
animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, TimeInterpolator interpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)419     public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
420             TimeInterpolator interpolator, final Runnable onCompleteRunnable,
421             final int animationEndStyle, View anchorView) {
422         // Clean up the previous animations
423         if (mDropAnim != null) mDropAnim.cancel();
424 
425         // Show the drop view if it was previously hidden
426         mDropView = view;
427         mDropView.cancelAnimation();
428         mDropView.requestLayout();
429 
430         // Set the anchor view if the page is scrolling
431         if (anchorView != null) {
432             mAnchorViewInitialScrollX = anchorView.getScrollX();
433         }
434         mAnchorView = anchorView;
435 
436         // Create and start the animation
437         mDropAnim = new ValueAnimator();
438         mDropAnim.setInterpolator(interpolator);
439         mDropAnim.setDuration(duration);
440         mDropAnim.setFloatValues(0f, 1f);
441         mDropAnim.addUpdateListener(updateCb);
442         mDropAnim.addListener(new AnimatorListenerAdapter() {
443             public void onAnimationEnd(Animator animation) {
444                 if (onCompleteRunnable != null) {
445                     onCompleteRunnable.run();
446                 }
447                 switch (animationEndStyle) {
448                 case ANIMATION_END_DISAPPEAR:
449                     clearAnimatedView();
450                     break;
451                 case ANIMATION_END_REMAIN_VISIBLE:
452                     break;
453                 }
454                 mDropAnim = null;
455             }
456         });
457         mDropAnim.start();
458     }
459 
clearAnimatedView()460     public void clearAnimatedView() {
461         if (mDropAnim != null) {
462             mDropAnim.cancel();
463         }
464         mDropAnim = null;
465         if (mDropView != null) {
466             mDragController.onDeferredEndDrag(mDropView);
467         }
468         mDropView = null;
469         invalidate();
470     }
471 
getAnimatedView()472     public View getAnimatedView() {
473         return mDropView;
474     }
475 
476     @Override
onViewAdded(View child)477     public void onViewAdded(View child) {
478         super.onViewAdded(child);
479         updateChildIndices();
480         UiFactory.onLauncherStateOrFocusChanged(mActivity);
481     }
482 
483     @Override
onViewRemoved(View child)484     public void onViewRemoved(View child) {
485         super.onViewRemoved(child);
486         updateChildIndices();
487         UiFactory.onLauncherStateOrFocusChanged(mActivity);
488     }
489 
490     @Override
bringChildToFront(View child)491     public void bringChildToFront(View child) {
492         super.bringChildToFront(child);
493         updateChildIndices();
494     }
495 
updateChildIndices()496     private void updateChildIndices() {
497         mTopViewIndex = -1;
498         int childCount = getChildCount();
499         for (int i = 0; i < childCount; i++) {
500             if (getChildAt(i) instanceof DragView) {
501                 mTopViewIndex = i;
502             }
503         }
504         mChildCountOnLastUpdate = childCount;
505     }
506 
507     @Override
getChildDrawingOrder(int childCount, int i)508     protected int getChildDrawingOrder(int childCount, int i) {
509         if (mChildCountOnLastUpdate != childCount) {
510             // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed.
511             // Pre-18, the child was not added / removed by the time of those callbacks. We need to
512             // force update our representation of things here to avoid crashing on pre-18 devices
513             // in certain instances.
514             updateChildIndices();
515         }
516 
517         // i represents the current draw iteration
518         if (mTopViewIndex == -1) {
519             // in general we do nothing
520             return i;
521         } else if (i == childCount - 1) {
522             // if we have a top index, we return it when drawing last item (highest z-order)
523             return mTopViewIndex;
524         } else if (i < mTopViewIndex) {
525             return i;
526         } else {
527             // for indexes greater than the top index, we fetch one item above to shift for the
528             // displacement of the top index
529             return i + 1;
530         }
531     }
532 
533     @Override
dispatchDraw(Canvas canvas)534     protected void dispatchDraw(Canvas canvas) {
535         // Draw the background below children.
536         mWorkspaceScrim.draw(canvas);
537         mOverviewScrim.updateCurrentScrimmedView(this);
538         mFocusIndicatorHelper.draw(canvas);
539         super.dispatchDraw(canvas);
540     }
541 
542     @Override
drawChild(Canvas canvas, View child, long drawingTime)543     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
544         if (child == mOverviewScrim.getScrimmedView()) {
545             mOverviewScrim.draw(canvas);
546         }
547         return super.drawChild(canvas, child, drawingTime);
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         mWorkspaceScrim.setSize(w, h);
554     }
555 
556     @Override
setInsets(Rect insets)557     public void setInsets(Rect insets) {
558         super.setInsets(insets);
559         mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
560         mOverviewScrim.onInsetsChanged(insets);
561     }
562 
getScrim()563     public WorkspaceAndHotseatScrim getScrim() {
564         return mWorkspaceScrim;
565     }
566 
getOverviewScrim()567     public OverviewScrim getOverviewScrim() {
568         return mOverviewScrim;
569     }
570 
571     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)572     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
573         RotationMode rotation = mActivity.getRotationMode();
574         int count = getChildCount();
575 
576         if (!rotation.isTransposed
577                 || getMode(widthMeasureSpec) != EXACTLY
578                 || getMode(heightMeasureSpec) != EXACTLY) {
579 
580             for (int i = 0; i < count; i++) {
581                 final View child = getChildAt(i);
582                 child.setRotation(rotation.surfaceRotation);
583             }
584             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
585         } else {
586 
587             for (int i = 0; i < count; i++) {
588                 final View child = getChildAt(i);
589                 if (child.getVisibility() == GONE) {
590                     continue;
591                 }
592                 if (!(child instanceof Transposable)) {
593                     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
594                 } else {
595                     measureChildWithMargins(child, heightMeasureSpec, 0, widthMeasureSpec, 0);
596 
597                     child.setPivotX(child.getMeasuredWidth() / 2);
598                     child.setPivotY(child.getMeasuredHeight() / 2);
599                     child.setRotation(rotation.surfaceRotation);
600                 }
601             }
602             setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
603         }
604     }
605 
606     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)607     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
608         RotationMode rotation = mActivity.getRotationMode();
609         if (!rotation.isTransposed) {
610             super.onLayout(changed, left, top, right, bottom);
611             return;
612         }
613 
614         final int count = getChildCount();
615 
616         final int parentWidth = right - left;
617         final int parentHeight = bottom - top;
618 
619         for (int i = 0; i < count; i++) {
620             final View child = getChildAt(i);
621             if (child.getVisibility() == GONE) {
622                 continue;
623             }
624 
625             final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
626 
627             if (lp instanceof LayoutParams) {
628                 final LayoutParams dlp = (LayoutParams) lp;
629                 if (dlp.customPosition) {
630                     child.layout(dlp.x, dlp.y, dlp.x + dlp.width, dlp.y + dlp.height);
631                     continue;
632                 }
633             }
634 
635             final int width = child.getMeasuredWidth();
636             final int height = child.getMeasuredHeight();
637 
638             int childLeft;
639             int childTop;
640 
641             int gravity = lp.gravity;
642             if (gravity == -1) {
643                 gravity = Gravity.TOP | Gravity.START;
644             }
645 
646             final int layoutDirection = getLayoutDirection();
647 
648             int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
649 
650             if (child instanceof Transposable) {
651                 absoluteGravity = rotation.toNaturalGravity(absoluteGravity);
652 
653                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
654                     case Gravity.CENTER_HORIZONTAL:
655                         childTop = (parentHeight - height) / 2 +
656                                 lp.topMargin - lp.bottomMargin;
657                         break;
658                     case Gravity.RIGHT:
659                         childTop = width / 2 + lp.rightMargin - height / 2;
660                         break;
661                     case Gravity.LEFT:
662                     default:
663                         childTop = parentHeight - lp.leftMargin - width / 2 - height / 2;
664                 }
665 
666                 switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
667                     case Gravity.CENTER_VERTICAL:
668                         childLeft = (parentWidth - width) / 2 +
669                                 lp.leftMargin - lp.rightMargin;
670                         break;
671                     case Gravity.BOTTOM:
672                         childLeft = parentWidth - width / 2 - height / 2 - lp.bottomMargin;
673                         break;
674                     case Gravity.TOP:
675                     default:
676                         childLeft = height / 2 - width / 2 + lp.topMargin;
677                 }
678             } else {
679                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
680                     case Gravity.CENTER_HORIZONTAL:
681                         childLeft = (parentWidth - width) / 2 +
682                                 lp.leftMargin - lp.rightMargin;
683                         break;
684                     case Gravity.RIGHT:
685                         childLeft = parentWidth - width - lp.rightMargin;
686                         break;
687                     case Gravity.LEFT:
688                     default:
689                         childLeft = lp.leftMargin;
690                 }
691 
692                 switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
693                     case Gravity.TOP:
694                         childTop = lp.topMargin;
695                         break;
696                     case Gravity.CENTER_VERTICAL:
697                         childTop = (parentHeight - height) / 2 +
698                                 lp.topMargin - lp.bottomMargin;
699                         break;
700                     case Gravity.BOTTOM:
701                         childTop = parentHeight - height - lp.bottomMargin;
702                         break;
703                     default:
704                         childTop = lp.topMargin;
705                 }
706             }
707 
708             child.layout(childLeft, childTop, childLeft + width, childTop + height);
709         }
710     }
711 }
712