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