1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher2;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.TimeInterpolator;
23 import android.animation.ValueAnimator;
24 import android.animation.ValueAnimator.AnimatorUpdateListener;
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.graphics.Bitmap;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.Paint;
32 import android.graphics.Point;
33 import android.graphics.PorterDuff;
34 import android.graphics.PorterDuffXfermode;
35 import android.graphics.Rect;
36 import android.graphics.drawable.ColorDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.graphics.drawable.NinePatchDrawable;
39 import android.os.Parcelable;
40 import android.util.AttributeSet;
41 import android.util.Log;
42 import android.util.SparseArray;
43 import android.view.MotionEvent;
44 import android.view.View;
45 import android.view.ViewDebug;
46 import android.view.ViewGroup;
47 import android.view.animation.Animation;
48 import android.view.animation.DecelerateInterpolator;
49 import android.view.animation.LayoutAnimationController;
50 
51 import com.android.launcher.R;
52 import com.android.launcher2.FolderIcon.FolderRingAnimator;
53 
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collections;
57 import java.util.Comparator;
58 import java.util.HashMap;
59 import java.util.Stack;
60 
61 public class CellLayout extends ViewGroup {
62     static final String TAG = "CellLayout";
63 
64     private Launcher mLauncher;
65     private int mCellWidth;
66     private int mCellHeight;
67 
68     private int mCountX;
69     private int mCountY;
70 
71     private int mOriginalWidthGap;
72     private int mOriginalHeightGap;
73     private int mWidthGap;
74     private int mHeightGap;
75     private int mMaxGap;
76     private boolean mScrollingTransformsDirty = false;
77 
78     private final Rect mRect = new Rect();
79     private final CellInfo mCellInfo = new CellInfo();
80 
81     // These are temporary variables to prevent having to allocate a new object just to
82     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
83     private final int[] mTmpXY = new int[2];
84     private final int[] mTmpPoint = new int[2];
85     int[] mTempLocation = new int[2];
86 
87     boolean[][] mOccupied;
88     boolean[][] mTmpOccupied;
89     private boolean mLastDownOnOccupiedCell = false;
90 
91     private OnTouchListener mInterceptTouchListener;
92 
93     private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
94     private int[] mFolderLeaveBehindCell = {-1, -1};
95 
96     private int mForegroundAlpha = 0;
97     private float mBackgroundAlpha;
98     private float mBackgroundAlphaMultiplier = 1.0f;
99 
100     private Drawable mNormalBackground;
101     private Drawable mActiveGlowBackground;
102     private Drawable mOverScrollForegroundDrawable;
103     private Drawable mOverScrollLeft;
104     private Drawable mOverScrollRight;
105     private Rect mBackgroundRect;
106     private Rect mForegroundRect;
107     private int mForegroundPadding;
108 
109     // If we're actively dragging something over this screen, mIsDragOverlapping is true
110     private boolean mIsDragOverlapping = false;
111     private final Point mDragCenter = new Point();
112 
113     // These arrays are used to implement the drag visualization on x-large screens.
114     // They are used as circular arrays, indexed by mDragOutlineCurrent.
115     private Rect[] mDragOutlines = new Rect[4];
116     private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
117     private InterruptibleInOutAnimator[] mDragOutlineAnims =
118             new InterruptibleInOutAnimator[mDragOutlines.length];
119 
120     // Used as an index into the above 3 arrays; indicates which is the most current value.
121     private int mDragOutlineCurrent = 0;
122     private final Paint mDragOutlinePaint = new Paint();
123 
124     private BubbleTextView mPressedOrFocusedIcon;
125 
126     private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
127             HashMap<CellLayout.LayoutParams, Animator>();
128     private HashMap<View, ReorderHintAnimation>
129             mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
130 
131     private boolean mItemPlacementDirty = false;
132 
133     // When a drag operation is in progress, holds the nearest cell to the touch point
134     private final int[] mDragCell = new int[2];
135 
136     private boolean mDragging = false;
137 
138     private TimeInterpolator mEaseOutInterpolator;
139     private ShortcutAndWidgetContainer mShortcutsAndWidgets;
140 
141     private boolean mIsHotseat = false;
142     private float mHotseatScale = 1f;
143 
144     public static final int MODE_DRAG_OVER = 0;
145     public static final int MODE_ON_DROP = 1;
146     public static final int MODE_ON_DROP_EXTERNAL = 2;
147     public static final int MODE_ACCEPT_DROP = 3;
148     private static final boolean DESTRUCTIVE_REORDER = false;
149     private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
150 
151     static final int LANDSCAPE = 0;
152     static final int PORTRAIT = 1;
153 
154     private static final float REORDER_HINT_MAGNITUDE = 0.12f;
155     private static final int REORDER_ANIMATION_DURATION = 150;
156     private float mReorderHintAnimationMagnitude;
157 
158     private ArrayList<View> mIntersectingViews = new ArrayList<View>();
159     private Rect mOccupiedRect = new Rect();
160     private int[] mDirectionVector = new int[2];
161     int[] mPreviousReorderDirection = new int[2];
162     private static final int INVALID_DIRECTION = -100;
163     private DropTarget.DragEnforcer mDragEnforcer;
164 
165     private final static PorterDuffXfermode sAddBlendMode =
166             new PorterDuffXfermode(PorterDuff.Mode.ADD);
167     private final static Paint sPaint = new Paint();
168 
CellLayout(Context context)169     public CellLayout(Context context) {
170         this(context, null);
171     }
172 
CellLayout(Context context, AttributeSet attrs)173     public CellLayout(Context context, AttributeSet attrs) {
174         this(context, attrs, 0);
175     }
176 
CellLayout(Context context, AttributeSet attrs, int defStyle)177     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
178         super(context, attrs, defStyle);
179         mDragEnforcer = new DropTarget.DragEnforcer(context);
180 
181         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
182         // the user where a dragged item will land when dropped.
183         setWillNotDraw(false);
184         setClipToPadding(false);
185         mLauncher = (Launcher) context;
186 
187         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
188 
189         mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
190         mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
191         mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
192         mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
193         mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
194         mCountX = LauncherModel.getCellCountX();
195         mCountY = LauncherModel.getCellCountY();
196         mOccupied = new boolean[mCountX][mCountY];
197         mTmpOccupied = new boolean[mCountX][mCountY];
198         mPreviousReorderDirection[0] = INVALID_DIRECTION;
199         mPreviousReorderDirection[1] = INVALID_DIRECTION;
200 
201         a.recycle();
202 
203         setAlwaysDrawnWithCacheEnabled(false);
204 
205         final Resources res = getResources();
206         mHotseatScale = (res.getInteger(R.integer.hotseat_item_scale_percentage) / 100f);
207 
208         mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
209         mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
210 
211         mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
212         mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
213         mForegroundPadding =
214                 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
215 
216         mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
217                 res.getDimensionPixelSize(R.dimen.app_icon_size));
218 
219         mNormalBackground.setFilterBitmap(true);
220         mActiveGlowBackground.setFilterBitmap(true);
221 
222         // Initialize the data structures used for the drag visualization.
223 
224         mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
225 
226 
227         mDragCell[0] = mDragCell[1] = -1;
228         for (int i = 0; i < mDragOutlines.length; i++) {
229             mDragOutlines[i] = new Rect(-1, -1, -1, -1);
230         }
231 
232         // When dragging things around the home screens, we show a green outline of
233         // where the item will land. The outlines gradually fade out, leaving a trail
234         // behind the drag path.
235         // Set up all the animations that are used to implement this fading.
236         final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
237         final float fromAlphaValue = 0;
238         final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
239 
240         Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
241 
242         for (int i = 0; i < mDragOutlineAnims.length; i++) {
243             final InterruptibleInOutAnimator anim =
244                 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
245             anim.getAnimator().setInterpolator(mEaseOutInterpolator);
246             final int thisIndex = i;
247             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
248                 public void onAnimationUpdate(ValueAnimator animation) {
249                     final Bitmap outline = (Bitmap)anim.getTag();
250 
251                     // If an animation is started and then stopped very quickly, we can still
252                     // get spurious updates we've cleared the tag. Guard against this.
253                     if (outline == null) {
254                         @SuppressWarnings("all") // suppress dead code warning
255                         final boolean debug = false;
256                         if (debug) {
257                             Object val = animation.getAnimatedValue();
258                             Log.d(TAG, "anim " + thisIndex + " update: " + val +
259                                      ", isStopped " + anim.isStopped());
260                         }
261                         // Try to prevent it from continuing to run
262                         animation.cancel();
263                     } else {
264                         mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
265                         CellLayout.this.invalidate(mDragOutlines[thisIndex]);
266                     }
267                 }
268             });
269             // The animation holds a reference to the drag outline bitmap as long is it's
270             // running. This way the bitmap can be GCed when the animations are complete.
271             anim.getAnimator().addListener(new AnimatorListenerAdapter() {
272                 @Override
273                 public void onAnimationEnd(Animator animation) {
274                     if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
275                         anim.setTag(null);
276                     }
277                 }
278             });
279             mDragOutlineAnims[i] = anim;
280         }
281 
282         mBackgroundRect = new Rect();
283         mForegroundRect = new Rect();
284 
285         mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
286         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
287                 mCountX);
288 
289         addView(mShortcutsAndWidgets);
290     }
291 
widthInPortrait(Resources r, int numCells)292     static int widthInPortrait(Resources r, int numCells) {
293         // We use this method from Workspace to figure out how many rows/columns Launcher should
294         // have. We ignore the left/right padding on CellLayout because it turns out in our design
295         // the padding extends outside the visible screen size, but it looked fine anyway.
296         int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
297         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
298                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
299 
300         return  minGap * (numCells - 1) + cellWidth * numCells;
301     }
302 
heightInLandscape(Resources r, int numCells)303     static int heightInLandscape(Resources r, int numCells) {
304         // We use this method from Workspace to figure out how many rows/columns Launcher should
305         // have. We ignore the left/right padding on CellLayout because it turns out in our design
306         // the padding extends outside the visible screen size, but it looked fine anyway.
307         int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
308         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
309                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
310 
311         return minGap * (numCells - 1) + cellHeight * numCells;
312     }
313 
enableHardwareLayers()314     public void enableHardwareLayers() {
315         mShortcutsAndWidgets.setLayerType(LAYER_TYPE_HARDWARE, sPaint);
316     }
317 
disableHardwareLayers()318     public void disableHardwareLayers() {
319         mShortcutsAndWidgets.setLayerType(LAYER_TYPE_NONE, sPaint);
320     }
321 
buildHardwareLayer()322     public void buildHardwareLayer() {
323         mShortcutsAndWidgets.buildLayer();
324     }
325 
getChildrenScale()326     public float getChildrenScale() {
327         return mIsHotseat ? mHotseatScale : 1.0f;
328     }
329 
setGridSize(int x, int y)330     public void setGridSize(int x, int y) {
331         mCountX = x;
332         mCountY = y;
333         mOccupied = new boolean[mCountX][mCountY];
334         mTmpOccupied = new boolean[mCountX][mCountY];
335         mTempRectStack.clear();
336         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
337                 mCountX);
338         requestLayout();
339     }
340 
341     // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
setInvertIfRtl(boolean invert)342     public void setInvertIfRtl(boolean invert) {
343         mShortcutsAndWidgets.setInvertIfRtl(invert);
344     }
345 
invalidateBubbleTextView(BubbleTextView icon)346     private void invalidateBubbleTextView(BubbleTextView icon) {
347         final int padding = icon.getPressedOrFocusedBackgroundPadding();
348         invalidate(icon.getLeft() + getPaddingLeft() - padding,
349                 icon.getTop() + getPaddingTop() - padding,
350                 icon.getRight() + getPaddingLeft() + padding,
351                 icon.getBottom() + getPaddingTop() + padding);
352     }
353 
setOverScrollAmount(float r, boolean left)354     void setOverScrollAmount(float r, boolean left) {
355         if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
356             mOverScrollForegroundDrawable = mOverScrollLeft;
357         } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
358             mOverScrollForegroundDrawable = mOverScrollRight;
359         }
360 
361         mForegroundAlpha = (int) Math.round((r * 255));
362         mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
363         invalidate();
364     }
365 
setPressedOrFocusedIcon(BubbleTextView icon)366     void setPressedOrFocusedIcon(BubbleTextView icon) {
367         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
368         // requires an expanded clip rect (due to the glow's blur radius)
369         BubbleTextView oldIcon = mPressedOrFocusedIcon;
370         mPressedOrFocusedIcon = icon;
371         if (oldIcon != null) {
372             invalidateBubbleTextView(oldIcon);
373         }
374         if (mPressedOrFocusedIcon != null) {
375             invalidateBubbleTextView(mPressedOrFocusedIcon);
376         }
377     }
378 
setIsDragOverlapping(boolean isDragOverlapping)379     void setIsDragOverlapping(boolean isDragOverlapping) {
380         if (mIsDragOverlapping != isDragOverlapping) {
381             mIsDragOverlapping = isDragOverlapping;
382             invalidate();
383         }
384     }
385 
getIsDragOverlapping()386     boolean getIsDragOverlapping() {
387         return mIsDragOverlapping;
388     }
389 
setOverscrollTransformsDirty(boolean dirty)390     protected void setOverscrollTransformsDirty(boolean dirty) {
391         mScrollingTransformsDirty = dirty;
392     }
393 
resetOverscrollTransforms()394     protected void resetOverscrollTransforms() {
395         if (mScrollingTransformsDirty) {
396             setOverscrollTransformsDirty(false);
397             setTranslationX(0);
398             setRotationY(0);
399             // It doesn't matter if we pass true or false here, the important thing is that we
400             // pass 0, which results in the overscroll drawable not being drawn any more.
401             setOverScrollAmount(0, false);
402             setPivotX(getMeasuredWidth() / 2);
403             setPivotY(getMeasuredHeight() / 2);
404         }
405     }
406 
scaleRect(Rect r, float scale)407     public void scaleRect(Rect r, float scale) {
408         if (scale != 1.0f) {
409             r.left = (int) (r.left * scale + 0.5f);
410             r.top = (int) (r.top * scale + 0.5f);
411             r.right = (int) (r.right * scale + 0.5f);
412             r.bottom = (int) (r.bottom * scale + 0.5f);
413         }
414     }
415 
416     Rect temp = new Rect();
scaleRectAboutCenter(Rect in, Rect out, float scale)417     void scaleRectAboutCenter(Rect in, Rect out, float scale) {
418         int cx = in.centerX();
419         int cy = in.centerY();
420         out.set(in);
421         out.offset(-cx, -cy);
422         scaleRect(out, scale);
423         out.offset(cx, cy);
424     }
425 
426     @Override
onDraw(Canvas canvas)427     protected void onDraw(Canvas canvas) {
428         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
429         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
430         // When we're small, we are either drawn normally or in the "accepts drops" state (during
431         // a drag). However, we also drag the mini hover background *over* one of those two
432         // backgrounds
433         if (mBackgroundAlpha > 0.0f) {
434             Drawable bg;
435 
436             if (mIsDragOverlapping) {
437                 // In the mini case, we draw the active_glow bg *over* the active background
438                 bg = mActiveGlowBackground;
439             } else {
440                 bg = mNormalBackground;
441             }
442 
443             bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
444             bg.setBounds(mBackgroundRect);
445             bg.draw(canvas);
446         }
447 
448         final Paint paint = mDragOutlinePaint;
449         for (int i = 0; i < mDragOutlines.length; i++) {
450             final float alpha = mDragOutlineAlphas[i];
451             if (alpha > 0) {
452                 final Rect r = mDragOutlines[i];
453                 scaleRectAboutCenter(r, temp, getChildrenScale());
454                 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
455                 paint.setAlpha((int)(alpha + .5f));
456                 canvas.drawBitmap(b, null, temp, paint);
457             }
458         }
459 
460         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
461         // requires an expanded clip rect (due to the glow's blur radius)
462         if (mPressedOrFocusedIcon != null) {
463             final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
464             final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
465             if (b != null) {
466                 canvas.drawBitmap(b,
467                         mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
468                         mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
469                         null);
470             }
471         }
472 
473         if (DEBUG_VISUALIZE_OCCUPIED) {
474             int[] pt = new int[2];
475             ColorDrawable cd = new ColorDrawable(Color.RED);
476             cd.setBounds(0, 0,  mCellWidth, mCellHeight);
477             for (int i = 0; i < mCountX; i++) {
478                 for (int j = 0; j < mCountY; j++) {
479                     if (mOccupied[i][j]) {
480                         cellToPoint(i, j, pt);
481                         canvas.save();
482                         canvas.translate(pt[0], pt[1]);
483                         cd.draw(canvas);
484                         canvas.restore();
485                     }
486                 }
487             }
488         }
489 
490         int previewOffset = FolderRingAnimator.sPreviewSize;
491 
492         // The folder outer / inner ring image(s)
493         for (int i = 0; i < mFolderOuterRings.size(); i++) {
494             FolderRingAnimator fra = mFolderOuterRings.get(i);
495 
496             // Draw outer ring
497             Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
498             int width = (int) fra.getOuterRingSize();
499             int height = width;
500             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
501 
502             int centerX = mTempLocation[0] + mCellWidth / 2;
503             int centerY = mTempLocation[1] + previewOffset / 2;
504 
505             canvas.save();
506             canvas.translate(centerX - width / 2, centerY - height / 2);
507             d.setBounds(0, 0, width, height);
508             d.draw(canvas);
509             canvas.restore();
510 
511             // Draw inner ring
512             d = FolderRingAnimator.sSharedInnerRingDrawable;
513             width = (int) fra.getInnerRingSize();
514             height = width;
515             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
516 
517             centerX = mTempLocation[0] + mCellWidth / 2;
518             centerY = mTempLocation[1] + previewOffset / 2;
519             canvas.save();
520             canvas.translate(centerX - width / 2, centerY - width / 2);
521             d.setBounds(0, 0, width, height);
522             d.draw(canvas);
523             canvas.restore();
524         }
525 
526         if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
527             Drawable d = FolderIcon.sSharedFolderLeaveBehind;
528             int width = d.getIntrinsicWidth();
529             int height = d.getIntrinsicHeight();
530 
531             cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
532             int centerX = mTempLocation[0] + mCellWidth / 2;
533             int centerY = mTempLocation[1] + previewOffset / 2;
534 
535             canvas.save();
536             canvas.translate(centerX - width / 2, centerY - width / 2);
537             d.setBounds(0, 0, width, height);
538             d.draw(canvas);
539             canvas.restore();
540         }
541     }
542 
543     @Override
dispatchDraw(Canvas canvas)544     protected void dispatchDraw(Canvas canvas) {
545         super.dispatchDraw(canvas);
546         if (mForegroundAlpha > 0) {
547             mOverScrollForegroundDrawable.setBounds(mForegroundRect);
548             Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
549             p.setXfermode(sAddBlendMode);
550             mOverScrollForegroundDrawable.draw(canvas);
551             p.setXfermode(null);
552         }
553     }
554 
showFolderAccept(FolderRingAnimator fra)555     public void showFolderAccept(FolderRingAnimator fra) {
556         mFolderOuterRings.add(fra);
557     }
558 
hideFolderAccept(FolderRingAnimator fra)559     public void hideFolderAccept(FolderRingAnimator fra) {
560         if (mFolderOuterRings.contains(fra)) {
561             mFolderOuterRings.remove(fra);
562         }
563         invalidate();
564     }
565 
setFolderLeaveBehindCell(int x, int y)566     public void setFolderLeaveBehindCell(int x, int y) {
567         mFolderLeaveBehindCell[0] = x;
568         mFolderLeaveBehindCell[1] = y;
569         invalidate();
570     }
571 
clearFolderLeaveBehind()572     public void clearFolderLeaveBehind() {
573         mFolderLeaveBehindCell[0] = -1;
574         mFolderLeaveBehindCell[1] = -1;
575         invalidate();
576     }
577 
578     @Override
shouldDelayChildPressedState()579     public boolean shouldDelayChildPressedState() {
580         return false;
581     }
582 
restoreInstanceState(SparseArray<Parcelable> states)583     public void restoreInstanceState(SparseArray<Parcelable> states) {
584         dispatchRestoreInstanceState(states);
585     }
586 
587     @Override
cancelLongPress()588     public void cancelLongPress() {
589         super.cancelLongPress();
590 
591         // Cancel long press for all children
592         final int count = getChildCount();
593         for (int i = 0; i < count; i++) {
594             final View child = getChildAt(i);
595             child.cancelLongPress();
596         }
597     }
598 
setOnInterceptTouchListener(View.OnTouchListener listener)599     public void setOnInterceptTouchListener(View.OnTouchListener listener) {
600         mInterceptTouchListener = listener;
601     }
602 
getCountX()603     int getCountX() {
604         return mCountX;
605     }
606 
getCountY()607     int getCountY() {
608         return mCountY;
609     }
610 
setIsHotseat(boolean isHotseat)611     public void setIsHotseat(boolean isHotseat) {
612         mIsHotseat = isHotseat;
613     }
614 
addViewToCellLayout(View child, int index, int childId, LayoutParams params, boolean markCells)615     public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
616             boolean markCells) {
617         final LayoutParams lp = params;
618 
619         // Hotseat icons - remove text
620         if (child instanceof BubbleTextView) {
621             BubbleTextView bubbleChild = (BubbleTextView) child;
622 
623             Resources res = getResources();
624             if (mIsHotseat) {
625                 bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
626             } else {
627                 bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
628             }
629         }
630 
631         child.setScaleX(getChildrenScale());
632         child.setScaleY(getChildrenScale());
633 
634         // Generate an id for each view, this assumes we have at most 256x256 cells
635         // per workspace screen
636         if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
637             // If the horizontal or vertical span is set to -1, it is taken to
638             // mean that it spans the extent of the CellLayout
639             if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
640             if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
641 
642             child.setId(childId);
643 
644             mShortcutsAndWidgets.addView(child, index, lp);
645 
646             if (markCells) markCellsAsOccupiedForView(child);
647 
648             return true;
649         }
650         return false;
651     }
652 
653     @Override
removeAllViews()654     public void removeAllViews() {
655         clearOccupiedCells();
656         mShortcutsAndWidgets.removeAllViews();
657     }
658 
659     @Override
removeAllViewsInLayout()660     public void removeAllViewsInLayout() {
661         if (mShortcutsAndWidgets.getChildCount() > 0) {
662             clearOccupiedCells();
663             mShortcutsAndWidgets.removeAllViewsInLayout();
664         }
665     }
666 
removeViewWithoutMarkingCells(View view)667     public void removeViewWithoutMarkingCells(View view) {
668         mShortcutsAndWidgets.removeView(view);
669     }
670 
671     @Override
removeView(View view)672     public void removeView(View view) {
673         markCellsAsUnoccupiedForView(view);
674         mShortcutsAndWidgets.removeView(view);
675     }
676 
677     @Override
removeViewAt(int index)678     public void removeViewAt(int index) {
679         markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
680         mShortcutsAndWidgets.removeViewAt(index);
681     }
682 
683     @Override
removeViewInLayout(View view)684     public void removeViewInLayout(View view) {
685         markCellsAsUnoccupiedForView(view);
686         mShortcutsAndWidgets.removeViewInLayout(view);
687     }
688 
689     @Override
removeViews(int start, int count)690     public void removeViews(int start, int count) {
691         for (int i = start; i < start + count; i++) {
692             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
693         }
694         mShortcutsAndWidgets.removeViews(start, count);
695     }
696 
697     @Override
removeViewsInLayout(int start, int count)698     public void removeViewsInLayout(int start, int count) {
699         for (int i = start; i < start + count; i++) {
700             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
701         }
702         mShortcutsAndWidgets.removeViewsInLayout(start, count);
703     }
704 
705     @Override
onAttachedToWindow()706     protected void onAttachedToWindow() {
707         super.onAttachedToWindow();
708         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
709     }
710 
setTagToCellInfoForPoint(int touchX, int touchY)711     public void setTagToCellInfoForPoint(int touchX, int touchY) {
712         final CellInfo cellInfo = mCellInfo;
713         Rect frame = mRect;
714         final int x = touchX + getScrollX();
715         final int y = touchY + getScrollY();
716         final int count = mShortcutsAndWidgets.getChildCount();
717 
718         boolean found = false;
719         for (int i = count - 1; i >= 0; i--) {
720             final View child = mShortcutsAndWidgets.getChildAt(i);
721             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
722 
723             if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
724                     lp.isLockedToGrid) {
725                 child.getHitRect(frame);
726 
727                 float scale = child.getScaleX();
728                 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
729                         child.getBottom());
730                 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
731                 // offset that by this CellLayout's padding to test an (x,y) point that is relative
732                 // to this view.
733                 frame.offset(getPaddingLeft(), getPaddingTop());
734                 frame.inset((int) (frame.width() * (1f - scale) / 2),
735                         (int) (frame.height() * (1f - scale) / 2));
736 
737                 if (frame.contains(x, y)) {
738                     cellInfo.cell = child;
739                     cellInfo.cellX = lp.cellX;
740                     cellInfo.cellY = lp.cellY;
741                     cellInfo.spanX = lp.cellHSpan;
742                     cellInfo.spanY = lp.cellVSpan;
743                     found = true;
744                     break;
745                 }
746             }
747         }
748 
749         mLastDownOnOccupiedCell = found;
750 
751         if (!found) {
752             final int cellXY[] = mTmpXY;
753             pointToCellExact(x, y, cellXY);
754 
755             cellInfo.cell = null;
756             cellInfo.cellX = cellXY[0];
757             cellInfo.cellY = cellXY[1];
758             cellInfo.spanX = 1;
759             cellInfo.spanY = 1;
760         }
761         setTag(cellInfo);
762     }
763 
764     @Override
onInterceptTouchEvent(MotionEvent ev)765     public boolean onInterceptTouchEvent(MotionEvent ev) {
766         // First we clear the tag to ensure that on every touch down we start with a fresh slate,
767         // even in the case where we return early. Not clearing here was causing bugs whereby on
768         // long-press we'd end up picking up an item from a previous drag operation.
769         final int action = ev.getAction();
770 
771         if (action == MotionEvent.ACTION_DOWN) {
772             clearTagCellInfo();
773         }
774 
775         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
776             return true;
777         }
778 
779         if (action == MotionEvent.ACTION_DOWN) {
780             setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
781         }
782 
783         return false;
784     }
785 
clearTagCellInfo()786     private void clearTagCellInfo() {
787         final CellInfo cellInfo = mCellInfo;
788         cellInfo.cell = null;
789         cellInfo.cellX = -1;
790         cellInfo.cellY = -1;
791         cellInfo.spanX = 0;
792         cellInfo.spanY = 0;
793         setTag(cellInfo);
794     }
795 
getTag()796     public CellInfo getTag() {
797         return (CellInfo) super.getTag();
798     }
799 
800     /**
801      * Given a point, return the cell that strictly encloses that point
802      * @param x X coordinate of the point
803      * @param y Y coordinate of the point
804      * @param result Array of 2 ints to hold the x and y coordinate of the cell
805      */
pointToCellExact(int x, int y, int[] result)806     void pointToCellExact(int x, int y, int[] result) {
807         final int hStartPadding = getPaddingLeft();
808         final int vStartPadding = getPaddingTop();
809 
810         result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
811         result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
812 
813         final int xAxis = mCountX;
814         final int yAxis = mCountY;
815 
816         if (result[0] < 0) result[0] = 0;
817         if (result[0] >= xAxis) result[0] = xAxis - 1;
818         if (result[1] < 0) result[1] = 0;
819         if (result[1] >= yAxis) result[1] = yAxis - 1;
820     }
821 
822     /**
823      * Given a point, return the cell that most closely encloses that point
824      * @param x X coordinate of the point
825      * @param y Y coordinate of the point
826      * @param result Array of 2 ints to hold the x and y coordinate of the cell
827      */
pointToCellRounded(int x, int y, int[] result)828     void pointToCellRounded(int x, int y, int[] result) {
829         pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
830     }
831 
832     /**
833      * Given a cell coordinate, return the point that represents the upper left corner of that cell
834      *
835      * @param cellX X coordinate of the cell
836      * @param cellY Y coordinate of the cell
837      *
838      * @param result Array of 2 ints to hold the x and y coordinate of the point
839      */
cellToPoint(int cellX, int cellY, int[] result)840     void cellToPoint(int cellX, int cellY, int[] result) {
841         final int hStartPadding = getPaddingLeft();
842         final int vStartPadding = getPaddingTop();
843 
844         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
845         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
846     }
847 
848     /**
849      * Given a cell coordinate, return the point that represents the center of the cell
850      *
851      * @param cellX X coordinate of the cell
852      * @param cellY Y coordinate of the cell
853      *
854      * @param result Array of 2 ints to hold the x and y coordinate of the point
855      */
cellToCenterPoint(int cellX, int cellY, int[] result)856     void cellToCenterPoint(int cellX, int cellY, int[] result) {
857         regionToCenterPoint(cellX, cellY, 1, 1, result);
858     }
859 
860     /**
861      * Given a cell coordinate and span return the point that represents the center of the regio
862      *
863      * @param cellX X coordinate of the cell
864      * @param cellY Y coordinate of the cell
865      *
866      * @param result Array of 2 ints to hold the x and y coordinate of the point
867      */
regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result)868     void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
869         final int hStartPadding = getPaddingLeft();
870         final int vStartPadding = getPaddingTop();
871         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
872                 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
873         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
874                 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
875     }
876 
877      /**
878      * Given a cell coordinate and span fills out a corresponding pixel rect
879      *
880      * @param cellX X coordinate of the cell
881      * @param cellY Y coordinate of the cell
882      * @param result Rect in which to write the result
883      */
regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result)884      void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
885         final int hStartPadding = getPaddingLeft();
886         final int vStartPadding = getPaddingTop();
887         final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
888         final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
889         result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
890                 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
891     }
892 
getDistanceFromCell(float x, float y, int[] cell)893     public float getDistanceFromCell(float x, float y, int[] cell) {
894         cellToCenterPoint(cell[0], cell[1], mTmpPoint);
895         float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
896                 Math.pow(y - mTmpPoint[1], 2));
897         return distance;
898     }
899 
getCellWidth()900     int getCellWidth() {
901         return mCellWidth;
902     }
903 
getCellHeight()904     int getCellHeight() {
905         return mCellHeight;
906     }
907 
getWidthGap()908     int getWidthGap() {
909         return mWidthGap;
910     }
911 
getHeightGap()912     int getHeightGap() {
913         return mHeightGap;
914     }
915 
getContentRect(Rect r)916     Rect getContentRect(Rect r) {
917         if (r == null) {
918             r = new Rect();
919         }
920         int left = getPaddingLeft();
921         int top = getPaddingTop();
922         int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
923         int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
924         r.set(left, top, right, bottom);
925         return r;
926     }
927 
getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight, int countX, int countY, int orientation)928     static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
929             int countX, int countY, int orientation) {
930         int numWidthGaps = countX - 1;
931         int numHeightGaps = countY - 1;
932 
933         int widthGap;
934         int heightGap;
935         int cellWidth;
936         int cellHeight;
937         int paddingLeft;
938         int paddingRight;
939         int paddingTop;
940         int paddingBottom;
941 
942         int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
943         if (orientation == LANDSCAPE) {
944             cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
945             cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
946             widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
947             heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
948             paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
949             paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
950             paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
951             paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
952         } else {
953             // PORTRAIT
954             cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
955             cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
956             widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
957             heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
958             paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
959             paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
960             paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
961             paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
962         }
963 
964         if (widthGap < 0 || heightGap < 0) {
965             int hSpace = measureWidth - paddingLeft - paddingRight;
966             int vSpace = measureHeight - paddingTop - paddingBottom;
967             int hFreeSpace = hSpace - (countX * cellWidth);
968             int vFreeSpace = vSpace - (countY * cellHeight);
969             widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
970             heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
971         }
972         metrics.set(cellWidth, cellHeight, widthGap, heightGap);
973     }
974 
975     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)976     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
977         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
978         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
979 
980         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
981         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
982 
983         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
984             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
985         }
986 
987         int numWidthGaps = mCountX - 1;
988         int numHeightGaps = mCountY - 1;
989 
990         if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
991             int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
992             int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
993             int hFreeSpace = hSpace - (mCountX * mCellWidth);
994             int vFreeSpace = vSpace - (mCountY * mCellHeight);
995             mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
996             mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
997             mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
998                     mCountX);
999         } else {
1000             mWidthGap = mOriginalWidthGap;
1001             mHeightGap = mOriginalHeightGap;
1002         }
1003 
1004         // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
1005         int newWidth = widthSpecSize;
1006         int newHeight = heightSpecSize;
1007         if (widthSpecMode == MeasureSpec.AT_MOST) {
1008             newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
1009                 ((mCountX - 1) * mWidthGap);
1010             newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
1011                 ((mCountY - 1) * mHeightGap);
1012             setMeasuredDimension(newWidth, newHeight);
1013         }
1014 
1015         int count = getChildCount();
1016         for (int i = 0; i < count; i++) {
1017             View child = getChildAt(i);
1018             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
1019                     getPaddingRight(), MeasureSpec.EXACTLY);
1020             int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
1021                     getPaddingBottom(), MeasureSpec.EXACTLY);
1022             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
1023         }
1024         setMeasuredDimension(newWidth, newHeight);
1025     }
1026 
1027     @Override
onLayout(boolean changed, int l, int t, int r, int b)1028     protected void onLayout(boolean changed, int l, int t, int r, int b) {
1029         int count = getChildCount();
1030         for (int i = 0; i < count; i++) {
1031             View child = getChildAt(i);
1032             child.layout(getPaddingLeft(), getPaddingTop(),
1033                     r - l - getPaddingRight(), b - t - getPaddingBottom());
1034         }
1035     }
1036 
1037     @Override
onSizeChanged(int w, int h, int oldw, int oldh)1038     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1039         super.onSizeChanged(w, h, oldw, oldh);
1040         mBackgroundRect.set(0, 0, w, h);
1041         mForegroundRect.set(mForegroundPadding, mForegroundPadding,
1042                 w - mForegroundPadding, h - mForegroundPadding);
1043     }
1044 
1045     @Override
setChildrenDrawingCacheEnabled(boolean enabled)1046     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
1047         mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
1048     }
1049 
1050     @Override
setChildrenDrawnWithCacheEnabled(boolean enabled)1051     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
1052         mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
1053     }
1054 
getBackgroundAlpha()1055     public float getBackgroundAlpha() {
1056         return mBackgroundAlpha;
1057     }
1058 
setBackgroundAlphaMultiplier(float multiplier)1059     public void setBackgroundAlphaMultiplier(float multiplier) {
1060         if (mBackgroundAlphaMultiplier != multiplier) {
1061             mBackgroundAlphaMultiplier = multiplier;
1062             invalidate();
1063         }
1064     }
1065 
getBackgroundAlphaMultiplier()1066     public float getBackgroundAlphaMultiplier() {
1067         return mBackgroundAlphaMultiplier;
1068     }
1069 
setBackgroundAlpha(float alpha)1070     public void setBackgroundAlpha(float alpha) {
1071         if (mBackgroundAlpha != alpha) {
1072             mBackgroundAlpha = alpha;
1073             invalidate();
1074         }
1075     }
1076 
setShortcutAndWidgetAlpha(float alpha)1077     public void setShortcutAndWidgetAlpha(float alpha) {
1078         final int childCount = getChildCount();
1079         for (int i = 0; i < childCount; i++) {
1080             getChildAt(i).setAlpha(alpha);
1081         }
1082     }
1083 
getShortcutsAndWidgets()1084     public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1085         if (getChildCount() > 0) {
1086             return (ShortcutAndWidgetContainer) getChildAt(0);
1087         }
1088         return null;
1089     }
1090 
getChildAt(int x, int y)1091     public View getChildAt(int x, int y) {
1092         return mShortcutsAndWidgets.getChildAt(x, y);
1093     }
1094 
animateChildToPosition(final View child, int cellX, int cellY, int duration, int delay, boolean permanent, boolean adjustOccupied)1095     public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
1096             int delay, boolean permanent, boolean adjustOccupied) {
1097         ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
1098         boolean[][] occupied = mOccupied;
1099         if (!permanent) {
1100             occupied = mTmpOccupied;
1101         }
1102 
1103         if (clc.indexOfChild(child) != -1) {
1104             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1105             final ItemInfo info = (ItemInfo) child.getTag();
1106 
1107             // We cancel any existing animations
1108             if (mReorderAnimators.containsKey(lp)) {
1109                 mReorderAnimators.get(lp).cancel();
1110                 mReorderAnimators.remove(lp);
1111             }
1112 
1113             final int oldX = lp.x;
1114             final int oldY = lp.y;
1115             if (adjustOccupied) {
1116                 occupied[lp.cellX][lp.cellY] = false;
1117                 occupied[cellX][cellY] = true;
1118             }
1119             lp.isLockedToGrid = true;
1120             if (permanent) {
1121                 lp.cellX = info.cellX = cellX;
1122                 lp.cellY = info.cellY = cellY;
1123             } else {
1124                 lp.tmpCellX = cellX;
1125                 lp.tmpCellY = cellY;
1126             }
1127             clc.setupLp(lp);
1128             lp.isLockedToGrid = false;
1129             final int newX = lp.x;
1130             final int newY = lp.y;
1131 
1132             lp.x = oldX;
1133             lp.y = oldY;
1134 
1135             // Exit early if we're not actually moving the view
1136             if (oldX == newX && oldY == newY) {
1137                 lp.isLockedToGrid = true;
1138                 return true;
1139             }
1140 
1141             ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
1142             va.setDuration(duration);
1143             mReorderAnimators.put(lp, va);
1144 
1145             va.addUpdateListener(new AnimatorUpdateListener() {
1146                 @Override
1147                 public void onAnimationUpdate(ValueAnimator animation) {
1148                     float r = ((Float) animation.getAnimatedValue()).floatValue();
1149                     lp.x = (int) ((1 - r) * oldX + r * newX);
1150                     lp.y = (int) ((1 - r) * oldY + r * newY);
1151                     child.requestLayout();
1152                 }
1153             });
1154             va.addListener(new AnimatorListenerAdapter() {
1155                 boolean cancelled = false;
1156                 public void onAnimationEnd(Animator animation) {
1157                     // If the animation was cancelled, it means that another animation
1158                     // has interrupted this one, and we don't want to lock the item into
1159                     // place just yet.
1160                     if (!cancelled) {
1161                         lp.isLockedToGrid = true;
1162                         child.requestLayout();
1163                     }
1164                     if (mReorderAnimators.containsKey(lp)) {
1165                         mReorderAnimators.remove(lp);
1166                     }
1167                 }
1168                 public void onAnimationCancel(Animator animation) {
1169                     cancelled = true;
1170                 }
1171             });
1172             va.setStartDelay(delay);
1173             va.start();
1174             return true;
1175         }
1176         return false;
1177     }
1178 
1179     /**
1180      * Estimate where the top left cell of the dragged item will land if it is dropped.
1181      *
1182      * @param originX The X value of the top left corner of the item
1183      * @param originY The Y value of the top left corner of the item
1184      * @param spanX The number of horizontal cells that the item spans
1185      * @param spanY The number of vertical cells that the item spans
1186      * @param result The estimated drop cell X and Y.
1187      */
estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result)1188     void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
1189         final int countX = mCountX;
1190         final int countY = mCountY;
1191 
1192         // pointToCellRounded takes the top left of a cell but will pad that with
1193         // cellWidth/2 and cellHeight/2 when finding the matching cell
1194         pointToCellRounded(originX, originY, result);
1195 
1196         // If the item isn't fully on this screen, snap to the edges
1197         int rightOverhang = result[0] + spanX - countX;
1198         if (rightOverhang > 0) {
1199             result[0] -= rightOverhang; // Snap to right
1200         }
1201         result[0] = Math.max(0, result[0]); // Snap to left
1202         int bottomOverhang = result[1] + spanY - countY;
1203         if (bottomOverhang > 0) {
1204             result[1] -= bottomOverhang; // Snap to bottom
1205         }
1206         result[1] = Math.max(0, result[1]); // Snap to top
1207     }
1208 
visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX, int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion)1209     void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1210             int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
1211         final int oldDragCellX = mDragCell[0];
1212         final int oldDragCellY = mDragCell[1];
1213 
1214         if (v != null && dragOffset == null) {
1215             mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1216         } else {
1217             mDragCenter.set(originX, originY);
1218         }
1219 
1220         if (dragOutline == null && v == null) {
1221             return;
1222         }
1223 
1224         if (cellX != oldDragCellX || cellY != oldDragCellY) {
1225             mDragCell[0] = cellX;
1226             mDragCell[1] = cellY;
1227             // Find the top left corner of the rect the object will occupy
1228             final int[] topLeft = mTmpPoint;
1229             cellToPoint(cellX, cellY, topLeft);
1230 
1231             int left = topLeft[0];
1232             int top = topLeft[1];
1233 
1234             if (v != null && dragOffset == null) {
1235                 // When drawing the drag outline, it did not account for margin offsets
1236                 // added by the view's parent.
1237                 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1238                 left += lp.leftMargin;
1239                 top += lp.topMargin;
1240 
1241                 // Offsets due to the size difference between the View and the dragOutline.
1242                 // There is a size difference to account for the outer blur, which may lie
1243                 // outside the bounds of the view.
1244                 top += (v.getHeight() - dragOutline.getHeight()) / 2;
1245                 // We center about the x axis
1246                 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1247                         - dragOutline.getWidth()) / 2;
1248             } else {
1249                 if (dragOffset != null && dragRegion != null) {
1250                     // Center the drag region *horizontally* in the cell and apply a drag
1251                     // outline offset
1252                     left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1253                              - dragRegion.width()) / 2;
1254                     top += dragOffset.y;
1255                 } else {
1256                     // Center the drag outline in the cell
1257                     left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1258                             - dragOutline.getWidth()) / 2;
1259                     top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1260                             - dragOutline.getHeight()) / 2;
1261                 }
1262             }
1263             final int oldIndex = mDragOutlineCurrent;
1264             mDragOutlineAnims[oldIndex].animateOut();
1265             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
1266             Rect r = mDragOutlines[mDragOutlineCurrent];
1267             r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1268             if (resize) {
1269                 cellToRect(cellX, cellY, spanX, spanY, r);
1270             }
1271 
1272             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1273             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
1274         }
1275     }
1276 
clearDragOutlines()1277     public void clearDragOutlines() {
1278         final int oldIndex = mDragOutlineCurrent;
1279         mDragOutlineAnims[oldIndex].animateOut();
1280         mDragCell[0] = mDragCell[1] = -1;
1281     }
1282 
1283     /**
1284      * Find a vacant area that will fit the given bounds nearest the requested
1285      * cell location. Uses Euclidean distance to score multiple vacant areas.
1286      *
1287      * @param pixelX The X location at which you want to search for a vacant area.
1288      * @param pixelY The Y location at which you want to search for a vacant area.
1289      * @param spanX Horizontal span of the object.
1290      * @param spanY Vertical span of the object.
1291      * @param result Array in which to place the result, or null (in which case a new array will
1292      *        be allocated)
1293      * @return The X, Y cell of a vacant area that can contain this object,
1294      *         nearest the requested location.
1295      */
findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result)1296     int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1297             int[] result) {
1298         return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
1299     }
1300 
1301     /**
1302      * Find a vacant area that will fit the given bounds nearest the requested
1303      * cell location. Uses Euclidean distance to score multiple vacant areas.
1304      *
1305      * @param pixelX The X location at which you want to search for a vacant area.
1306      * @param pixelY The Y location at which you want to search for a vacant area.
1307      * @param minSpanX The minimum horizontal span required
1308      * @param minSpanY The minimum vertical span required
1309      * @param spanX Horizontal span of the object.
1310      * @param spanY Vertical span of the object.
1311      * @param result Array in which to place the result, or null (in which case a new array will
1312      *        be allocated)
1313      * @return The X, Y cell of a vacant area that can contain this object,
1314      *         nearest the requested location.
1315      */
findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] result, int[] resultSpan)1316     int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1317             int spanY, int[] result, int[] resultSpan) {
1318         return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1319                 result, resultSpan);
1320     }
1321 
1322     /**
1323      * Find a vacant area that will fit the given bounds nearest the requested
1324      * cell location. Uses Euclidean distance to score multiple vacant areas.
1325      *
1326      * @param pixelX The X location at which you want to search for a vacant area.
1327      * @param pixelY The Y location at which you want to search for a vacant area.
1328      * @param spanX Horizontal span of the object.
1329      * @param spanY Vertical span of the object.
1330      * @param ignoreOccupied If true, the result can be an occupied cell
1331      * @param result Array in which to place the result, or null (in which case a new array will
1332      *        be allocated)
1333      * @return The X, Y cell of a vacant area that can contain this object,
1334      *         nearest the requested location.
1335      */
findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, boolean ignoreOccupied, int[] result)1336     int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1337             boolean ignoreOccupied, int[] result) {
1338         return findNearestArea(pixelX, pixelY, spanX, spanY,
1339                 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
1340     }
1341 
1342     private final Stack<Rect> mTempRectStack = new Stack<Rect>();
lazyInitTempRectStack()1343     private void lazyInitTempRectStack() {
1344         if (mTempRectStack.isEmpty()) {
1345             for (int i = 0; i < mCountX * mCountY; i++) {
1346                 mTempRectStack.push(new Rect());
1347             }
1348         }
1349     }
1350 
recycleTempRects(Stack<Rect> used)1351     private void recycleTempRects(Stack<Rect> used) {
1352         while (!used.isEmpty()) {
1353             mTempRectStack.push(used.pop());
1354         }
1355     }
1356 
1357     /**
1358      * Find a vacant area that will fit the given bounds nearest the requested
1359      * cell location. Uses Euclidean distance to score multiple vacant areas.
1360      *
1361      * @param pixelX The X location at which you want to search for a vacant area.
1362      * @param pixelY The Y location at which you want to search for a vacant area.
1363      * @param minSpanX The minimum horizontal span required
1364      * @param minSpanY The minimum vertical span required
1365      * @param spanX Horizontal span of the object.
1366      * @param spanY Vertical span of the object.
1367      * @param ignoreOccupied If true, the result can be an occupied cell
1368      * @param result Array in which to place the result, or null (in which case a new array will
1369      *        be allocated)
1370      * @return The X, Y cell of a vacant area that can contain this object,
1371      *         nearest the requested location.
1372      */
findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan, boolean[][] occupied)1373     int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
1374             View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1375             boolean[][] occupied) {
1376         lazyInitTempRectStack();
1377         // mark space take by ignoreView as available (method checks if ignoreView is null)
1378         markCellsAsUnoccupiedForView(ignoreView, occupied);
1379 
1380         // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1381         // to the center of the item, but we are searching based on the top-left cell, so
1382         // we translate the point over to correspond to the top-left.
1383         pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1384         pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1385 
1386         // Keep track of best-scoring drop area
1387         final int[] bestXY = result != null ? result : new int[2];
1388         double bestDistance = Double.MAX_VALUE;
1389         final Rect bestRect = new Rect(-1, -1, -1, -1);
1390         final Stack<Rect> validRegions = new Stack<Rect>();
1391 
1392         final int countX = mCountX;
1393         final int countY = mCountY;
1394 
1395         if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1396                 spanX < minSpanX || spanY < minSpanY) {
1397             return bestXY;
1398         }
1399 
1400         for (int y = 0; y < countY - (minSpanY - 1); y++) {
1401             inner:
1402             for (int x = 0; x < countX - (minSpanX - 1); x++) {
1403                 int ySize = -1;
1404                 int xSize = -1;
1405                 if (ignoreOccupied) {
1406                     // First, let's see if this thing fits anywhere
1407                     for (int i = 0; i < minSpanX; i++) {
1408                         for (int j = 0; j < minSpanY; j++) {
1409                             if (occupied[x + i][y + j]) {
1410                                 continue inner;
1411                             }
1412                         }
1413                     }
1414                     xSize = minSpanX;
1415                     ySize = minSpanY;
1416 
1417                     // We know that the item will fit at _some_ acceptable size, now let's see
1418                     // how big we can make it. We'll alternate between incrementing x and y spans
1419                     // until we hit a limit.
1420                     boolean incX = true;
1421                     boolean hitMaxX = xSize >= spanX;
1422                     boolean hitMaxY = ySize >= spanY;
1423                     while (!(hitMaxX && hitMaxY)) {
1424                         if (incX && !hitMaxX) {
1425                             for (int j = 0; j < ySize; j++) {
1426                                 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1427                                     // We can't move out horizontally
1428                                     hitMaxX = true;
1429                                 }
1430                             }
1431                             if (!hitMaxX) {
1432                                 xSize++;
1433                             }
1434                         } else if (!hitMaxY) {
1435                             for (int i = 0; i < xSize; i++) {
1436                                 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1437                                     // We can't move out vertically
1438                                     hitMaxY = true;
1439                                 }
1440                             }
1441                             if (!hitMaxY) {
1442                                 ySize++;
1443                             }
1444                         }
1445                         hitMaxX |= xSize >= spanX;
1446                         hitMaxY |= ySize >= spanY;
1447                         incX = !incX;
1448                     }
1449                     incX = true;
1450                     hitMaxX = xSize >= spanX;
1451                     hitMaxY = ySize >= spanY;
1452                 }
1453                 final int[] cellXY = mTmpXY;
1454                 cellToCenterPoint(x, y, cellXY);
1455 
1456                 // We verify that the current rect is not a sub-rect of any of our previous
1457                 // candidates. In this case, the current rect is disqualified in favour of the
1458                 // containing rect.
1459                 Rect currentRect = mTempRectStack.pop();
1460                 currentRect.set(x, y, x + xSize, y + ySize);
1461                 boolean contained = false;
1462                 for (Rect r : validRegions) {
1463                     if (r.contains(currentRect)) {
1464                         contained = true;
1465                         break;
1466                     }
1467                 }
1468                 validRegions.push(currentRect);
1469                 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1470                         + Math.pow(cellXY[1] - pixelY, 2));
1471 
1472                 if ((distance <= bestDistance && !contained) ||
1473                         currentRect.contains(bestRect)) {
1474                     bestDistance = distance;
1475                     bestXY[0] = x;
1476                     bestXY[1] = y;
1477                     if (resultSpan != null) {
1478                         resultSpan[0] = xSize;
1479                         resultSpan[1] = ySize;
1480                     }
1481                     bestRect.set(currentRect);
1482                 }
1483             }
1484         }
1485         // re-mark space taken by ignoreView as occupied
1486         markCellsAsOccupiedForView(ignoreView, occupied);
1487 
1488         // Return -1, -1 if no suitable location found
1489         if (bestDistance == Double.MAX_VALUE) {
1490             bestXY[0] = -1;
1491             bestXY[1] = -1;
1492         }
1493         recycleTempRects(validRegions);
1494         return bestXY;
1495     }
1496 
1497      /**
1498      * Find a vacant area that will fit the given bounds nearest the requested
1499      * cell location, and will also weigh in a suggested direction vector of the
1500      * desired location. This method computers distance based on unit grid distances,
1501      * not pixel distances.
1502      *
1503      * @param cellX The X cell nearest to which you want to search for a vacant area.
1504      * @param cellY The Y cell nearest which you want to search for a vacant area.
1505      * @param spanX Horizontal span of the object.
1506      * @param spanY Vertical span of the object.
1507      * @param direction The favored direction in which the views should move from x, y
1508      * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1509      *        matches exactly. Otherwise we find the best matching direction.
1510      * @param occoupied The array which represents which cells in the CellLayout are occupied
1511      * @param blockOccupied The array which represents which cells in the specified block (cellX,
1512      *        cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
1513      * @param result Array in which to place the result, or null (in which case a new array will
1514      *        be allocated)
1515      * @return The X, Y cell of a vacant area that can contain this object,
1516      *         nearest the requested location.
1517      */
findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, boolean[][] occupied, boolean blockOccupied[][], int[] result)1518     private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
1519             boolean[][] occupied, boolean blockOccupied[][], int[] result) {
1520         // Keep track of best-scoring drop area
1521         final int[] bestXY = result != null ? result : new int[2];
1522         float bestDistance = Float.MAX_VALUE;
1523         int bestDirectionScore = Integer.MIN_VALUE;
1524 
1525         final int countX = mCountX;
1526         final int countY = mCountY;
1527 
1528         for (int y = 0; y < countY - (spanY - 1); y++) {
1529             inner:
1530             for (int x = 0; x < countX - (spanX - 1); x++) {
1531                 // First, let's see if this thing fits anywhere
1532                 for (int i = 0; i < spanX; i++) {
1533                     for (int j = 0; j < spanY; j++) {
1534                         if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1535                             continue inner;
1536                         }
1537                     }
1538                 }
1539 
1540                 float distance = (float)
1541                         Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1542                 int[] curDirection = mTmpPoint;
1543                 computeDirectionVector(x - cellX, y - cellY, curDirection);
1544                 // The direction score is just the dot product of the two candidate direction
1545                 // and that passed in.
1546                 int curDirectionScore = direction[0] * curDirection[0] +
1547                         direction[1] * curDirection[1];
1548                 boolean exactDirectionOnly = false;
1549                 boolean directionMatches = direction[0] == curDirection[0] &&
1550                         direction[0] == curDirection[0];
1551                 if ((directionMatches || !exactDirectionOnly) &&
1552                         Float.compare(distance,  bestDistance) < 0 || (Float.compare(distance,
1553                         bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1554                     bestDistance = distance;
1555                     bestDirectionScore = curDirectionScore;
1556                     bestXY[0] = x;
1557                     bestXY[1] = y;
1558                 }
1559             }
1560         }
1561 
1562         // Return -1, -1 if no suitable location found
1563         if (bestDistance == Float.MAX_VALUE) {
1564             bestXY[0] = -1;
1565             bestXY[1] = -1;
1566         }
1567         return bestXY;
1568     }
1569 
addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, int[] direction, ItemConfiguration currentState)1570     private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
1571             int[] direction, ItemConfiguration currentState) {
1572         CellAndSpan c = currentState.map.get(v);
1573         boolean success = false;
1574         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1575         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1576 
1577         findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
1578 
1579         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
1580             c.x = mTempLocation[0];
1581             c.y = mTempLocation[1];
1582             success = true;
1583         }
1584         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1585         return success;
1586     }
1587 
1588     /**
1589      * This helper class defines a cluster of views. It helps with defining complex edges
1590      * of the cluster and determining how those edges interact with other views. The edges
1591      * essentially define a fine-grained boundary around the cluster of views -- like a more
1592      * precise version of a bounding box.
1593      */
1594     private class ViewCluster {
1595         final static int LEFT = 0;
1596         final static int TOP = 1;
1597         final static int RIGHT = 2;
1598         final static int BOTTOM = 3;
1599 
1600         ArrayList<View> views;
1601         ItemConfiguration config;
1602         Rect boundingRect = new Rect();
1603 
1604         int[] leftEdge = new int[mCountY];
1605         int[] rightEdge = new int[mCountY];
1606         int[] topEdge = new int[mCountX];
1607         int[] bottomEdge = new int[mCountX];
1608         boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1609 
1610         @SuppressWarnings("unchecked")
ViewCluster(ArrayList<View> views, ItemConfiguration config)1611         public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1612             this.views = (ArrayList<View>) views.clone();
1613             this.config = config;
1614             resetEdges();
1615         }
1616 
resetEdges()1617         void resetEdges() {
1618             for (int i = 0; i < mCountX; i++) {
1619                 topEdge[i] = -1;
1620                 bottomEdge[i] = -1;
1621             }
1622             for (int i = 0; i < mCountY; i++) {
1623                 leftEdge[i] = -1;
1624                 rightEdge[i] = -1;
1625             }
1626             leftEdgeDirty = true;
1627             rightEdgeDirty = true;
1628             bottomEdgeDirty = true;
1629             topEdgeDirty = true;
1630             boundingRectDirty = true;
1631         }
1632 
computeEdge(int which, int[] edge)1633         void computeEdge(int which, int[] edge) {
1634             int count = views.size();
1635             for (int i = 0; i < count; i++) {
1636                 CellAndSpan cs = config.map.get(views.get(i));
1637                 switch (which) {
1638                     case LEFT:
1639                         int left = cs.x;
1640                         for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1641                             if (left < edge[j] || edge[j] < 0) {
1642                                 edge[j] = left;
1643                             }
1644                         }
1645                         break;
1646                     case RIGHT:
1647                         int right = cs.x + cs.spanX;
1648                         for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1649                             if (right > edge[j]) {
1650                                 edge[j] = right;
1651                             }
1652                         }
1653                         break;
1654                     case TOP:
1655                         int top = cs.y;
1656                         for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1657                             if (top < edge[j] || edge[j] < 0) {
1658                                 edge[j] = top;
1659                             }
1660                         }
1661                         break;
1662                     case BOTTOM:
1663                         int bottom = cs.y + cs.spanY;
1664                         for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1665                             if (bottom > edge[j]) {
1666                                 edge[j] = bottom;
1667                             }
1668                         }
1669                         break;
1670                 }
1671             }
1672         }
1673 
isViewTouchingEdge(View v, int whichEdge)1674         boolean isViewTouchingEdge(View v, int whichEdge) {
1675             CellAndSpan cs = config.map.get(v);
1676 
1677             int[] edge = getEdge(whichEdge);
1678 
1679             switch (whichEdge) {
1680                 case LEFT:
1681                     for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1682                         if (edge[i] == cs.x + cs.spanX) {
1683                             return true;
1684                         }
1685                     }
1686                     break;
1687                 case RIGHT:
1688                     for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1689                         if (edge[i] == cs.x) {
1690                             return true;
1691                         }
1692                     }
1693                     break;
1694                 case TOP:
1695                     for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1696                         if (edge[i] == cs.y + cs.spanY) {
1697                             return true;
1698                         }
1699                     }
1700                     break;
1701                 case BOTTOM:
1702                     for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1703                         if (edge[i] == cs.y) {
1704                             return true;
1705                         }
1706                     }
1707                     break;
1708             }
1709             return false;
1710         }
1711 
shift(int whichEdge, int delta)1712         void shift(int whichEdge, int delta) {
1713             for (View v: views) {
1714                 CellAndSpan c = config.map.get(v);
1715                 switch (whichEdge) {
1716                     case LEFT:
1717                         c.x -= delta;
1718                         break;
1719                     case RIGHT:
1720                         c.x += delta;
1721                         break;
1722                     case TOP:
1723                         c.y -= delta;
1724                         break;
1725                     case BOTTOM:
1726                     default:
1727                         c.y += delta;
1728                         break;
1729                 }
1730             }
1731             resetEdges();
1732         }
1733 
addView(View v)1734         public void addView(View v) {
1735             views.add(v);
1736             resetEdges();
1737         }
1738 
getBoundingRect()1739         public Rect getBoundingRect() {
1740             if (boundingRectDirty) {
1741                 boolean first = true;
1742                 for (View v: views) {
1743                     CellAndSpan c = config.map.get(v);
1744                     if (first) {
1745                         boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1746                         first = false;
1747                     } else {
1748                         boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1749                     }
1750                 }
1751             }
1752             return boundingRect;
1753         }
1754 
getEdge(int which)1755         public int[] getEdge(int which) {
1756             switch (which) {
1757                 case LEFT:
1758                     return getLeftEdge();
1759                 case RIGHT:
1760                     return getRightEdge();
1761                 case TOP:
1762                     return getTopEdge();
1763                 case BOTTOM:
1764                 default:
1765                     return getBottomEdge();
1766             }
1767         }
1768 
getLeftEdge()1769         public int[] getLeftEdge() {
1770             if (leftEdgeDirty) {
1771                 computeEdge(LEFT, leftEdge);
1772             }
1773             return leftEdge;
1774         }
1775 
getRightEdge()1776         public int[] getRightEdge() {
1777             if (rightEdgeDirty) {
1778                 computeEdge(RIGHT, rightEdge);
1779             }
1780             return rightEdge;
1781         }
1782 
getTopEdge()1783         public int[] getTopEdge() {
1784             if (topEdgeDirty) {
1785                 computeEdge(TOP, topEdge);
1786             }
1787             return topEdge;
1788         }
1789 
getBottomEdge()1790         public int[] getBottomEdge() {
1791             if (bottomEdgeDirty) {
1792                 computeEdge(BOTTOM, bottomEdge);
1793             }
1794             return bottomEdge;
1795         }
1796 
1797         PositionComparator comparator = new PositionComparator();
1798         class PositionComparator implements Comparator<View> {
1799             int whichEdge = 0;
compare(View left, View right)1800             public int compare(View left, View right) {
1801                 CellAndSpan l = config.map.get(left);
1802                 CellAndSpan r = config.map.get(right);
1803                 switch (whichEdge) {
1804                     case LEFT:
1805                         return (r.x + r.spanX) - (l.x + l.spanX);
1806                     case RIGHT:
1807                         return l.x - r.x;
1808                     case TOP:
1809                         return (r.y + r.spanY) - (l.y + l.spanY);
1810                     case BOTTOM:
1811                     default:
1812                         return l.y - r.y;
1813                 }
1814             }
1815         }
1816 
sortConfigurationForEdgePush(int edge)1817         public void sortConfigurationForEdgePush(int edge) {
1818             comparator.whichEdge = edge;
1819             Collections.sort(config.sortedViews, comparator);
1820         }
1821     }
1822 
pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, int[] direction, View dragView, ItemConfiguration currentState)1823     private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1824             int[] direction, View dragView, ItemConfiguration currentState) {
1825 
1826         ViewCluster cluster = new ViewCluster(views, currentState);
1827         Rect clusterRect = cluster.getBoundingRect();
1828         int whichEdge;
1829         int pushDistance;
1830         boolean fail = false;
1831 
1832         // Determine the edge of the cluster that will be leading the push and how far
1833         // the cluster must be shifted.
1834         if (direction[0] < 0) {
1835             whichEdge = ViewCluster.LEFT;
1836             pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
1837         } else if (direction[0] > 0) {
1838             whichEdge = ViewCluster.RIGHT;
1839             pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1840         } else if (direction[1] < 0) {
1841             whichEdge = ViewCluster.TOP;
1842             pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1843         } else {
1844             whichEdge = ViewCluster.BOTTOM;
1845             pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
1846         }
1847 
1848         // Break early for invalid push distance.
1849         if (pushDistance <= 0) {
1850             return false;
1851         }
1852 
1853         // Mark the occupied state as false for the group of views we want to move.
1854         for (View v: views) {
1855             CellAndSpan c = currentState.map.get(v);
1856             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1857         }
1858 
1859         // We save the current configuration -- if we fail to find a solution we will revert
1860         // to the initial state. The process of finding a solution modifies the configuration
1861         // in place, hence the need for revert in the failure case.
1862         currentState.save();
1863 
1864         // The pushing algorithm is simplified by considering the views in the order in which
1865         // they would be pushed by the cluster. For example, if the cluster is leading with its
1866         // left edge, we consider sort the views by their right edge, from right to left.
1867         cluster.sortConfigurationForEdgePush(whichEdge);
1868 
1869         while (pushDistance > 0 && !fail) {
1870             for (View v: currentState.sortedViews) {
1871                 // For each view that isn't in the cluster, we see if the leading edge of the
1872                 // cluster is contacting the edge of that view. If so, we add that view to the
1873                 // cluster.
1874                 if (!cluster.views.contains(v) && v != dragView) {
1875                     if (cluster.isViewTouchingEdge(v, whichEdge)) {
1876                         LayoutParams lp = (LayoutParams) v.getLayoutParams();
1877                         if (!lp.canReorder) {
1878                             // The push solution includes the all apps button, this is not viable.
1879                             fail = true;
1880                             break;
1881                         }
1882                         cluster.addView(v);
1883                         CellAndSpan c = currentState.map.get(v);
1884 
1885                         // Adding view to cluster, mark it as not occupied.
1886                         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1887                     }
1888                 }
1889             }
1890             pushDistance--;
1891 
1892             // The cluster has been completed, now we move the whole thing over in the appropriate
1893             // direction.
1894             cluster.shift(whichEdge, 1);
1895         }
1896 
1897         boolean foundSolution = false;
1898         clusterRect = cluster.getBoundingRect();
1899 
1900         // Due to the nature of the algorithm, the only check required to verify a valid solution
1901         // is to ensure that completed shifted cluster lies completely within the cell layout.
1902         if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1903                 clusterRect.bottom <= mCountY) {
1904             foundSolution = true;
1905         } else {
1906             currentState.restore();
1907         }
1908 
1909         // In either case, we set the occupied array as marked for the location of the views
1910         for (View v: cluster.views) {
1911             CellAndSpan c = currentState.map.get(v);
1912             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1913         }
1914 
1915         return foundSolution;
1916     }
1917 
addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, int[] direction, View dragView, ItemConfiguration currentState)1918     private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1919             int[] direction, View dragView, ItemConfiguration currentState) {
1920         if (views.size() == 0) return true;
1921 
1922         boolean success = false;
1923         Rect boundingRect = null;
1924         // We construct a rect which represents the entire group of views passed in
1925         for (View v: views) {
1926             CellAndSpan c = currentState.map.get(v);
1927             if (boundingRect == null) {
1928                 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1929             } else {
1930                 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1931             }
1932         }
1933 
1934         // Mark the occupied state as false for the group of views we want to move.
1935         for (View v: views) {
1936             CellAndSpan c = currentState.map.get(v);
1937             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1938         }
1939 
1940         boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1941         int top = boundingRect.top;
1942         int left = boundingRect.left;
1943         // We mark more precisely which parts of the bounding rect are truly occupied, allowing
1944         // for interlocking.
1945         for (View v: views) {
1946             CellAndSpan c = currentState.map.get(v);
1947             markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
1948         }
1949 
1950         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1951 
1952         findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1953                 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1954 
1955         // If we successfuly found a location by pushing the block of views, we commit it
1956         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
1957             int deltaX = mTempLocation[0] - boundingRect.left;
1958             int deltaY = mTempLocation[1] - boundingRect.top;
1959             for (View v: views) {
1960                 CellAndSpan c = currentState.map.get(v);
1961                 c.x += deltaX;
1962                 c.y += deltaY;
1963             }
1964             success = true;
1965         }
1966 
1967         // In either case, we set the occupied array as marked for the location of the views
1968         for (View v: views) {
1969             CellAndSpan c = currentState.map.get(v);
1970             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1971         }
1972         return success;
1973     }
1974 
markCellsForRect(Rect r, boolean[][] occupied, boolean value)1975     private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1976         markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1977     }
1978 
1979     // This method tries to find a reordering solution which satisfies the push mechanic by trying
1980     // to push items in each of the cardinal directions, in an order based on the direction vector
1981     // passed.
attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied, int[] direction, View ignoreView, ItemConfiguration solution)1982     private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1983             int[] direction, View ignoreView, ItemConfiguration solution) {
1984         if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
1985             // If the direction vector has two non-zero components, we try pushing
1986             // separately in each of the components.
1987             int temp = direction[1];
1988             direction[1] = 0;
1989 
1990             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
1991                     ignoreView, solution)) {
1992                 return true;
1993             }
1994             direction[1] = temp;
1995             temp = direction[0];
1996             direction[0] = 0;
1997 
1998             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
1999                     ignoreView, solution)) {
2000                 return true;
2001             }
2002             // Revert the direction
2003             direction[0] = temp;
2004 
2005             // Now we try pushing in each component of the opposite direction
2006             direction[0] *= -1;
2007             direction[1] *= -1;
2008             temp = direction[1];
2009             direction[1] = 0;
2010             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2011                     ignoreView, solution)) {
2012                 return true;
2013             }
2014 
2015             direction[1] = temp;
2016             temp = direction[0];
2017             direction[0] = 0;
2018             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2019                     ignoreView, solution)) {
2020                 return true;
2021             }
2022             // revert the direction
2023             direction[0] = temp;
2024             direction[0] *= -1;
2025             direction[1] *= -1;
2026 
2027         } else {
2028             // If the direction vector has a single non-zero component, we push first in the
2029             // direction of the vector
2030             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2031                     ignoreView, solution)) {
2032                 return true;
2033             }
2034             // Then we try the opposite direction
2035             direction[0] *= -1;
2036             direction[1] *= -1;
2037             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2038                     ignoreView, solution)) {
2039                 return true;
2040             }
2041             // Switch the direction back
2042             direction[0] *= -1;
2043             direction[1] *= -1;
2044 
2045             // If we have failed to find a push solution with the above, then we try
2046             // to find a solution by pushing along the perpendicular axis.
2047 
2048             // Swap the components
2049             int temp = direction[1];
2050             direction[1] = direction[0];
2051             direction[0] = temp;
2052             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2053                     ignoreView, solution)) {
2054                 return true;
2055             }
2056 
2057             // Then we try the opposite direction
2058             direction[0] *= -1;
2059             direction[1] *= -1;
2060             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
2061                     ignoreView, solution)) {
2062                 return true;
2063             }
2064             // Switch the direction back
2065             direction[0] *= -1;
2066             direction[1] *= -1;
2067 
2068             // Swap the components back
2069             temp = direction[1];
2070             direction[1] = direction[0];
2071             direction[0] = temp;
2072         }
2073         return false;
2074     }
2075 
rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, View ignoreView, ItemConfiguration solution)2076     private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
2077             View ignoreView, ItemConfiguration solution) {
2078         // Return early if get invalid cell positions
2079         if (cellX < 0 || cellY < 0) return false;
2080 
2081         mIntersectingViews.clear();
2082         mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2083 
2084         // Mark the desired location of the view currently being dragged.
2085         if (ignoreView != null) {
2086             CellAndSpan c = solution.map.get(ignoreView);
2087             if (c != null) {
2088                 c.x = cellX;
2089                 c.y = cellY;
2090             }
2091         }
2092         Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2093         Rect r1 = new Rect();
2094         for (View child: solution.map.keySet()) {
2095             if (child == ignoreView) continue;
2096             CellAndSpan c = solution.map.get(child);
2097             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2098             r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
2099             if (Rect.intersects(r0, r1)) {
2100                 if (!lp.canReorder) {
2101                     return false;
2102                 }
2103                 mIntersectingViews.add(child);
2104             }
2105         }
2106 
2107         // First we try to find a solution which respects the push mechanic. That is,
2108         // we try to find a solution such that no displaced item travels through another item
2109         // without also displacing that item.
2110         if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2111                 solution)) {
2112             return true;
2113         }
2114 
2115         // Next we try moving the views as a block, but without requiring the push mechanic.
2116         if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
2117                 solution)) {
2118             return true;
2119         }
2120 
2121         // Ok, they couldn't move as a block, let's move them individually
2122         for (View v : mIntersectingViews) {
2123             if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
2124                 return false;
2125             }
2126         }
2127         return true;
2128     }
2129 
2130     /*
2131      * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2132      * the provided point and the provided cell
2133      */
computeDirectionVector(float deltaX, float deltaY, int[] result)2134     private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
2135         double angle = Math.atan(((float) deltaY) / deltaX);
2136 
2137         result[0] = 0;
2138         result[1] = 0;
2139         if (Math.abs(Math.cos(angle)) > 0.5f) {
2140             result[0] = (int) Math.signum(deltaX);
2141         }
2142         if (Math.abs(Math.sin(angle)) > 0.5f) {
2143             result[1] = (int) Math.signum(deltaY);
2144         }
2145     }
2146 
copyOccupiedArray(boolean[][] occupied)2147     private void copyOccupiedArray(boolean[][] occupied) {
2148         for (int i = 0; i < mCountX; i++) {
2149             for (int j = 0; j < mCountY; j++) {
2150                 occupied[i][j] = mOccupied[i][j];
2151             }
2152         }
2153     }
2154 
simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution)2155     ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
2156             int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
2157         // Copy the current state into the solution. This solution will be manipulated as necessary.
2158         copyCurrentStateToSolution(solution, false);
2159         // Copy the current occupied array into the temporary occupied array. This array will be
2160         // manipulated as necessary to find a solution.
2161         copyOccupiedArray(mTmpOccupied);
2162 
2163         // We find the nearest cell into which we would place the dragged item, assuming there's
2164         // nothing in its way.
2165         int result[] = new int[2];
2166         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2167 
2168         boolean success = false;
2169         // First we try the exact nearest position of the item being dragged,
2170         // we will then want to try to move this around to other neighbouring positions
2171         success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2172                 solution);
2173 
2174         if (!success) {
2175             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2176             // x, then 1 in y etc.
2177             if (spanX > minSpanX && (minSpanY == spanY || decX)) {
2178                 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
2179                         dragView, false, solution);
2180             } else if (spanY > minSpanY) {
2181                 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
2182                         dragView, true, solution);
2183             }
2184             solution.isSolution = false;
2185         } else {
2186             solution.isSolution = true;
2187             solution.dragViewX = result[0];
2188             solution.dragViewY = result[1];
2189             solution.dragViewSpanX = spanX;
2190             solution.dragViewSpanY = spanY;
2191         }
2192         return solution;
2193     }
2194 
copyCurrentStateToSolution(ItemConfiguration solution, boolean temp)2195     private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
2196         int childCount = mShortcutsAndWidgets.getChildCount();
2197         for (int i = 0; i < childCount; i++) {
2198             View child = mShortcutsAndWidgets.getChildAt(i);
2199             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2200             CellAndSpan c;
2201             if (temp) {
2202                 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
2203             } else {
2204                 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
2205             }
2206             solution.add(child, c);
2207         }
2208     }
2209 
copySolutionToTempState(ItemConfiguration solution, View dragView)2210     private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2211         for (int i = 0; i < mCountX; i++) {
2212             for (int j = 0; j < mCountY; j++) {
2213                 mTmpOccupied[i][j] = false;
2214             }
2215         }
2216 
2217         int childCount = mShortcutsAndWidgets.getChildCount();
2218         for (int i = 0; i < childCount; i++) {
2219             View child = mShortcutsAndWidgets.getChildAt(i);
2220             if (child == dragView) continue;
2221             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2222             CellAndSpan c = solution.map.get(child);
2223             if (c != null) {
2224                 lp.tmpCellX = c.x;
2225                 lp.tmpCellY = c.y;
2226                 lp.cellHSpan = c.spanX;
2227                 lp.cellVSpan = c.spanY;
2228                 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
2229             }
2230         }
2231         markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2232                 solution.dragViewSpanY, mTmpOccupied, true);
2233     }
2234 
animateItemsToSolution(ItemConfiguration solution, View dragView, boolean commitDragView)2235     private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2236             commitDragView) {
2237 
2238         boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2239         for (int i = 0; i < mCountX; i++) {
2240             for (int j = 0; j < mCountY; j++) {
2241                 occupied[i][j] = false;
2242             }
2243         }
2244 
2245         int childCount = mShortcutsAndWidgets.getChildCount();
2246         for (int i = 0; i < childCount; i++) {
2247             View child = mShortcutsAndWidgets.getChildAt(i);
2248             if (child == dragView) continue;
2249             CellAndSpan c = solution.map.get(child);
2250             if (c != null) {
2251                 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2252                         DESTRUCTIVE_REORDER, false);
2253                 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
2254             }
2255         }
2256         if (commitDragView) {
2257             markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2258                     solution.dragViewSpanY, occupied, true);
2259         }
2260     }
2261 
2262     // This method starts or changes the reorder hint animations
beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay)2263     private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
2264         int childCount = mShortcutsAndWidgets.getChildCount();
2265         for (int i = 0; i < childCount; i++) {
2266             View child = mShortcutsAndWidgets.getChildAt(i);
2267             if (child == dragView) continue;
2268             CellAndSpan c = solution.map.get(child);
2269             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2270             if (c != null) {
2271                 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
2272                         c.x, c.y, c.spanX, c.spanY);
2273                 rha.animate();
2274             }
2275         }
2276     }
2277 
2278     // Class which represents the reorder hint animations. These animations show that an item is
2279     // in a temporary state, and hint at where the item will return to.
2280     class ReorderHintAnimation {
2281         View child;
2282         float finalDeltaX;
2283         float finalDeltaY;
2284         float initDeltaX;
2285         float initDeltaY;
2286         float finalScale;
2287         float initScale;
2288         private static final int DURATION = 300;
2289         Animator a;
2290 
ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1, int spanX, int spanY)2291         public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
2292                 int spanX, int spanY) {
2293             regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2294             final int x0 = mTmpPoint[0];
2295             final int y0 = mTmpPoint[1];
2296             regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2297             final int x1 = mTmpPoint[0];
2298             final int y1 = mTmpPoint[1];
2299             final int dX = x1 - x0;
2300             final int dY = y1 - y0;
2301             finalDeltaX = 0;
2302             finalDeltaY = 0;
2303             if (dX == dY && dX == 0) {
2304             } else {
2305                 if (dY == 0) {
2306                     finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
2307                 } else if (dX == 0) {
2308                     finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
2309                 } else {
2310                     double angle = Math.atan( (float) (dY) / dX);
2311                     finalDeltaX = (int) (- Math.signum(dX) *
2312                             Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
2313                     finalDeltaY = (int) (- Math.signum(dY) *
2314                             Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
2315                 }
2316             }
2317             initDeltaX = child.getTranslationX();
2318             initDeltaY = child.getTranslationY();
2319             finalScale = getChildrenScale() - 4.0f / child.getWidth();
2320             initScale = child.getScaleX();
2321             this.child = child;
2322         }
2323 
animate()2324         void animate() {
2325             if (mShakeAnimators.containsKey(child)) {
2326                 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
2327                 oldAnimation.cancel();
2328                 mShakeAnimators.remove(child);
2329                 if (finalDeltaX == 0 && finalDeltaY == 0) {
2330                     completeAnimationImmediately();
2331                     return;
2332                 }
2333             }
2334             if (finalDeltaX == 0 && finalDeltaY == 0) {
2335                 return;
2336             }
2337             ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
2338             a = va;
2339             va.setRepeatMode(ValueAnimator.REVERSE);
2340             va.setRepeatCount(ValueAnimator.INFINITE);
2341             va.setDuration(DURATION);
2342             va.setStartDelay((int) (Math.random() * 60));
2343             va.addUpdateListener(new AnimatorUpdateListener() {
2344                 @Override
2345                 public void onAnimationUpdate(ValueAnimator animation) {
2346                     float r = ((Float) animation.getAnimatedValue()).floatValue();
2347                     float x = r * finalDeltaX + (1 - r) * initDeltaX;
2348                     float y = r * finalDeltaY + (1 - r) * initDeltaY;
2349                     child.setTranslationX(x);
2350                     child.setTranslationY(y);
2351                     float s = r * finalScale + (1 - r) * initScale;
2352                     child.setScaleX(s);
2353                     child.setScaleY(s);
2354                 }
2355             });
2356             va.addListener(new AnimatorListenerAdapter() {
2357                 public void onAnimationRepeat(Animator animation) {
2358                     // We make sure to end only after a full period
2359                     initDeltaX = 0;
2360                     initDeltaY = 0;
2361                     initScale = getChildrenScale();
2362                 }
2363             });
2364             mShakeAnimators.put(child, this);
2365             va.start();
2366         }
2367 
cancel()2368         private void cancel() {
2369             if (a != null) {
2370                 a.cancel();
2371             }
2372         }
2373 
completeAnimationImmediately()2374         private void completeAnimationImmediately() {
2375             if (a != null) {
2376                 a.cancel();
2377             }
2378 
2379             AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
2380             a = s;
2381             s.playTogether(
2382                 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2383                 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
2384                 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2385                 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
2386             );
2387             s.setDuration(REORDER_ANIMATION_DURATION);
2388             s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2389             s.start();
2390         }
2391     }
2392 
completeAndClearReorderHintAnimations()2393     private void completeAndClearReorderHintAnimations() {
2394         for (ReorderHintAnimation a: mShakeAnimators.values()) {
2395             a.completeAnimationImmediately();
2396         }
2397         mShakeAnimators.clear();
2398     }
2399 
commitTempPlacement()2400     private void commitTempPlacement() {
2401         for (int i = 0; i < mCountX; i++) {
2402             for (int j = 0; j < mCountY; j++) {
2403                 mOccupied[i][j] = mTmpOccupied[i][j];
2404             }
2405         }
2406         int childCount = mShortcutsAndWidgets.getChildCount();
2407         for (int i = 0; i < childCount; i++) {
2408             View child = mShortcutsAndWidgets.getChildAt(i);
2409             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2410             ItemInfo info = (ItemInfo) child.getTag();
2411             // We do a null check here because the item info can be null in the case of the
2412             // AllApps button in the hotseat.
2413             if (info != null) {
2414                 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2415                         info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2416                     info.requiresDbUpdate = true;
2417                 }
2418                 info.cellX = lp.cellX = lp.tmpCellX;
2419                 info.cellY = lp.cellY = lp.tmpCellY;
2420                 info.spanX = lp.cellHSpan;
2421                 info.spanY = lp.cellVSpan;
2422             }
2423         }
2424         mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
2425     }
2426 
setUseTempCoords(boolean useTempCoords)2427     public void setUseTempCoords(boolean useTempCoords) {
2428         int childCount = mShortcutsAndWidgets.getChildCount();
2429         for (int i = 0; i < childCount; i++) {
2430             LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
2431             lp.useTmpCoords = useTempCoords;
2432         }
2433     }
2434 
findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView, ItemConfiguration solution)2435     ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2436             int spanX, int spanY, View dragView, ItemConfiguration solution) {
2437         int[] result = new int[2];
2438         int[] resultSpan = new int[2];
2439         findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2440                 resultSpan);
2441         if (result[0] >= 0 && result[1] >= 0) {
2442             copyCurrentStateToSolution(solution, false);
2443             solution.dragViewX = result[0];
2444             solution.dragViewY = result[1];
2445             solution.dragViewSpanX = resultSpan[0];
2446             solution.dragViewSpanY = resultSpan[1];
2447             solution.isSolution = true;
2448         } else {
2449             solution.isSolution = false;
2450         }
2451         return solution;
2452     }
2453 
prepareChildForDrag(View child)2454     public void prepareChildForDrag(View child) {
2455         markCellsAsUnoccupiedForView(child);
2456     }
2457 
2458     /* This seems like it should be obvious and straight-forward, but when the direction vector
2459     needs to match with the notion of the dragView pushing other views, we have to employ
2460     a slightly more subtle notion of the direction vector. The question is what two points is
2461     the vector between? The center of the dragView and its desired destination? Not quite, as
2462     this doesn't necessarily coincide with the interaction of the dragView and items occupying
2463     those cells. Instead we use some heuristics to often lock the vector to up, down, left
2464     or right, which helps make pushing feel right.
2465     */
getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX, int spanY, View dragView, int[] resultDirection)2466     private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2467             int spanY, View dragView, int[] resultDirection) {
2468         int[] targetDestination = new int[2];
2469 
2470         findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2471         Rect dragRect = new Rect();
2472         regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2473         dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2474 
2475         Rect dropRegionRect = new Rect();
2476         getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2477                 dragView, dropRegionRect, mIntersectingViews);
2478 
2479         int dropRegionSpanX = dropRegionRect.width();
2480         int dropRegionSpanY = dropRegionRect.height();
2481 
2482         regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2483                 dropRegionRect.height(), dropRegionRect);
2484 
2485         int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2486         int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2487 
2488         if (dropRegionSpanX == mCountX || spanX == mCountX) {
2489             deltaX = 0;
2490         }
2491         if (dropRegionSpanY == mCountY || spanY == mCountY) {
2492             deltaY = 0;
2493         }
2494 
2495         if (deltaX == 0 && deltaY == 0) {
2496             // No idea what to do, give a random direction.
2497             resultDirection[0] = 1;
2498             resultDirection[1] = 0;
2499         } else {
2500             computeDirectionVector(deltaX, deltaY, resultDirection);
2501         }
2502     }
2503 
2504     // For a given cell and span, fetch the set of views intersecting the region.
getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY, View dragView, Rect boundingRect, ArrayList<View> intersectingViews)2505     private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2506             View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2507         if (boundingRect != null) {
2508             boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2509         }
2510         intersectingViews.clear();
2511         Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2512         Rect r1 = new Rect();
2513         final int count = mShortcutsAndWidgets.getChildCount();
2514         for (int i = 0; i < count; i++) {
2515             View child = mShortcutsAndWidgets.getChildAt(i);
2516             if (child == dragView) continue;
2517             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2518             r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2519             if (Rect.intersects(r0, r1)) {
2520                 mIntersectingViews.add(child);
2521                 if (boundingRect != null) {
2522                     boundingRect.union(r1);
2523                 }
2524             }
2525         }
2526     }
2527 
isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, View dragView, int[] result)2528     boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2529             View dragView, int[] result) {
2530         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2531         getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2532                 mIntersectingViews);
2533         return !mIntersectingViews.isEmpty();
2534     }
2535 
revertTempState()2536     void revertTempState() {
2537         if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2538         final int count = mShortcutsAndWidgets.getChildCount();
2539         for (int i = 0; i < count; i++) {
2540             View child = mShortcutsAndWidgets.getChildAt(i);
2541             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2542             if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2543                 lp.tmpCellX = lp.cellX;
2544                 lp.tmpCellY = lp.cellY;
2545                 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2546                         0, false, false);
2547             }
2548         }
2549         completeAndClearReorderHintAnimations();
2550         setItemPlacementDirty(false);
2551     }
2552 
createAreaForResize(int cellX, int cellY, int spanX, int spanY, View dragView, int[] direction, boolean commit)2553     boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2554             View dragView, int[] direction, boolean commit) {
2555         int[] pixelXY = new int[2];
2556         regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2557 
2558         // First we determine if things have moved enough to cause a different layout
2559         ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2560                  spanX,  spanY, direction, dragView,  true,  new ItemConfiguration());
2561 
2562         setUseTempCoords(true);
2563         if (swapSolution != null && swapSolution.isSolution) {
2564             // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2565             // committing anything or animating anything as we just want to determine if a solution
2566             // exists
2567             copySolutionToTempState(swapSolution, dragView);
2568             setItemPlacementDirty(true);
2569             animateItemsToSolution(swapSolution, dragView, commit);
2570 
2571             if (commit) {
2572                 commitTempPlacement();
2573                 completeAndClearReorderHintAnimations();
2574                 setItemPlacementDirty(false);
2575             } else {
2576                 beginOrAdjustHintAnimations(swapSolution, dragView,
2577                         REORDER_ANIMATION_DURATION);
2578             }
2579             mShortcutsAndWidgets.requestLayout();
2580         }
2581         return swapSolution.isSolution;
2582     }
2583 
createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView, int[] result, int resultSpan[], int mode)2584     int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2585             View dragView, int[] result, int resultSpan[], int mode) {
2586         // First we determine if things have moved enough to cause a different layout
2587         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2588 
2589         if (resultSpan == null) {
2590             resultSpan = new int[2];
2591         }
2592 
2593         // When we are checking drop validity or actually dropping, we don't recompute the
2594         // direction vector, since we want the solution to match the preview, and it's possible
2595         // that the exact position of the item has changed to result in a new reordering outcome.
2596         if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2597                && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
2598             mDirectionVector[0] = mPreviousReorderDirection[0];
2599             mDirectionVector[1] = mPreviousReorderDirection[1];
2600             // We reset this vector after drop
2601             if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2602                 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2603                 mPreviousReorderDirection[1] = INVALID_DIRECTION;
2604             }
2605         } else {
2606             getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2607             mPreviousReorderDirection[0] = mDirectionVector[0];
2608             mPreviousReorderDirection[1] = mDirectionVector[1];
2609         }
2610 
2611         ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2612                  spanX,  spanY, mDirectionVector, dragView,  true,  new ItemConfiguration());
2613 
2614         // We attempt the approach which doesn't shuffle views at all
2615         ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2616                 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2617 
2618         ItemConfiguration finalSolution = null;
2619         if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2620             finalSolution = swapSolution;
2621         } else if (noShuffleSolution.isSolution) {
2622             finalSolution = noShuffleSolution;
2623         }
2624 
2625         boolean foundSolution = true;
2626         if (!DESTRUCTIVE_REORDER) {
2627             setUseTempCoords(true);
2628         }
2629 
2630         if (finalSolution != null) {
2631             result[0] = finalSolution.dragViewX;
2632             result[1] = finalSolution.dragViewY;
2633             resultSpan[0] = finalSolution.dragViewSpanX;
2634             resultSpan[1] = finalSolution.dragViewSpanY;
2635 
2636             // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2637             // committing anything or animating anything as we just want to determine if a solution
2638             // exists
2639             if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2640                 if (!DESTRUCTIVE_REORDER) {
2641                     copySolutionToTempState(finalSolution, dragView);
2642                 }
2643                 setItemPlacementDirty(true);
2644                 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2645 
2646                 if (!DESTRUCTIVE_REORDER &&
2647                         (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
2648                     commitTempPlacement();
2649                     completeAndClearReorderHintAnimations();
2650                     setItemPlacementDirty(false);
2651                 } else {
2652                     beginOrAdjustHintAnimations(finalSolution, dragView,
2653                             REORDER_ANIMATION_DURATION);
2654                 }
2655             }
2656         } else {
2657             foundSolution = false;
2658             result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2659         }
2660 
2661         if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2662             setUseTempCoords(false);
2663         }
2664 
2665         mShortcutsAndWidgets.requestLayout();
2666         return result;
2667     }
2668 
setItemPlacementDirty(boolean dirty)2669     void setItemPlacementDirty(boolean dirty) {
2670         mItemPlacementDirty = dirty;
2671     }
isItemPlacementDirty()2672     boolean isItemPlacementDirty() {
2673         return mItemPlacementDirty;
2674     }
2675 
2676     private class ItemConfiguration {
2677         HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
2678         private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2679         ArrayList<View> sortedViews = new ArrayList<View>();
2680         boolean isSolution = false;
2681         int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2682 
save()2683         void save() {
2684             // Copy current state into savedMap
2685             for (View v: map.keySet()) {
2686                 map.get(v).copy(savedMap.get(v));
2687             }
2688         }
2689 
restore()2690         void restore() {
2691             // Restore current state from savedMap
2692             for (View v: savedMap.keySet()) {
2693                 savedMap.get(v).copy(map.get(v));
2694             }
2695         }
2696 
add(View v, CellAndSpan cs)2697         void add(View v, CellAndSpan cs) {
2698             map.put(v, cs);
2699             savedMap.put(v, new CellAndSpan());
2700             sortedViews.add(v);
2701         }
2702 
area()2703         int area() {
2704             return dragViewSpanX * dragViewSpanY;
2705         }
2706     }
2707 
2708     private class CellAndSpan {
2709         int x, y;
2710         int spanX, spanY;
2711 
CellAndSpan()2712         public CellAndSpan() {
2713         }
2714 
copy(CellAndSpan copy)2715         public void copy(CellAndSpan copy) {
2716             copy.x = x;
2717             copy.y = y;
2718             copy.spanX = spanX;
2719             copy.spanY = spanY;
2720         }
2721 
CellAndSpan(int x, int y, int spanX, int spanY)2722         public CellAndSpan(int x, int y, int spanX, int spanY) {
2723             this.x = x;
2724             this.y = y;
2725             this.spanX = spanX;
2726             this.spanY = spanY;
2727         }
2728 
toString()2729         public String toString() {
2730             return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2731         }
2732 
2733     }
2734 
2735     /**
2736      * Find a vacant area that will fit the given bounds nearest the requested
2737      * cell location. Uses Euclidean distance to score multiple vacant areas.
2738      *
2739      * @param pixelX The X location at which you want to search for a vacant area.
2740      * @param pixelY The Y location at which you want to search for a vacant area.
2741      * @param spanX Horizontal span of the object.
2742      * @param spanY Vertical span of the object.
2743      * @param ignoreView Considers space occupied by this view as unoccupied
2744      * @param result Previously returned value to possibly recycle.
2745      * @return The X, Y cell of a vacant area that can contain this object,
2746      *         nearest the requested location.
2747      */
findNearestVacantArea( int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result)2748     int[] findNearestVacantArea(
2749             int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2750         return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2751     }
2752 
2753     /**
2754      * Find a vacant area that will fit the given bounds nearest the requested
2755      * cell location. Uses Euclidean distance to score multiple vacant areas.
2756      *
2757      * @param pixelX The X location at which you want to search for a vacant area.
2758      * @param pixelY The Y location at which you want to search for a vacant area.
2759      * @param minSpanX The minimum horizontal span required
2760      * @param minSpanY The minimum vertical span required
2761      * @param spanX Horizontal span of the object.
2762      * @param spanY Vertical span of the object.
2763      * @param ignoreView Considers space occupied by this view as unoccupied
2764      * @param result Previously returned value to possibly recycle.
2765      * @return The X, Y cell of a vacant area that can contain this object,
2766      *         nearest the requested location.
2767      */
findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan)2768     int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2769             int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
2770         return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2771                 result, resultSpan, mOccupied);
2772     }
2773 
2774     /**
2775      * Find a starting cell position that will fit the given bounds nearest the requested
2776      * cell location. Uses Euclidean distance to score multiple vacant areas.
2777      *
2778      * @param pixelX The X location at which you want to search for a vacant area.
2779      * @param pixelY The Y location at which you want to search for a vacant area.
2780      * @param spanX Horizontal span of the object.
2781      * @param spanY Vertical span of the object.
2782      * @param ignoreView Considers space occupied by this view as unoccupied
2783      * @param result Previously returned value to possibly recycle.
2784      * @return The X, Y cell of a vacant area that can contain this object,
2785      *         nearest the requested location.
2786      */
findNearestArea( int pixelX, int pixelY, int spanX, int spanY, int[] result)2787     int[] findNearestArea(
2788             int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2789         return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2790     }
2791 
existsEmptyCell()2792     boolean existsEmptyCell() {
2793         return findCellForSpan(null, 1, 1);
2794     }
2795 
2796     /**
2797      * Finds the upper-left coordinate of the first rectangle in the grid that can
2798      * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2799      * then this method will only return coordinates for rectangles that contain the cell
2800      * (intersectX, intersectY)
2801      *
2802      * @param cellXY The array that will contain the position of a vacant cell if such a cell
2803      *               can be found.
2804      * @param spanX The horizontal span of the cell we want to find.
2805      * @param spanY The vertical span of the cell we want to find.
2806      *
2807      * @return True if a vacant cell of the specified dimension was found, false otherwise.
2808      */
findCellForSpan(int[] cellXY, int spanX, int spanY)2809     boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
2810         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
2811     }
2812 
2813     /**
2814      * Like above, but ignores any cells occupied by the item "ignoreView"
2815      *
2816      * @param cellXY The array that will contain the position of a vacant cell if such a cell
2817      *               can be found.
2818      * @param spanX The horizontal span of the cell we want to find.
2819      * @param spanY The vertical span of the cell we want to find.
2820      * @param ignoreView The home screen item we should treat as not occupying any space
2821      * @return
2822      */
findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView)2823     boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
2824         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2825                 ignoreView, mOccupied);
2826     }
2827 
2828     /**
2829      * Like above, but if intersectX and intersectY are not -1, then this method will try to
2830      * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2831      *
2832      * @param spanX The horizontal span of the cell we want to find.
2833      * @param spanY The vertical span of the cell we want to find.
2834      * @param ignoreView The home screen item we should treat as not occupying any space
2835      * @param intersectX The X coordinate of the cell that we should try to overlap
2836      * @param intersectX The Y coordinate of the cell that we should try to overlap
2837      *
2838      * @return True if a vacant cell of the specified dimension was found, false otherwise.
2839      */
findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, int intersectX, int intersectY)2840     boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2841             int intersectX, int intersectY) {
2842         return findCellForSpanThatIntersectsIgnoring(
2843                 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
2844     }
2845 
2846     /**
2847      * The superset of the above two methods
2848      */
findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, int intersectX, int intersectY, View ignoreView, boolean occupied[][])2849     boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
2850             int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
2851         // mark space take by ignoreView as available (method checks if ignoreView is null)
2852         markCellsAsUnoccupiedForView(ignoreView, occupied);
2853 
2854         boolean foundCell = false;
2855         while (true) {
2856             int startX = 0;
2857             if (intersectX >= 0) {
2858                 startX = Math.max(startX, intersectX - (spanX - 1));
2859             }
2860             int endX = mCountX - (spanX - 1);
2861             if (intersectX >= 0) {
2862                 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2863             }
2864             int startY = 0;
2865             if (intersectY >= 0) {
2866                 startY = Math.max(startY, intersectY - (spanY - 1));
2867             }
2868             int endY = mCountY - (spanY - 1);
2869             if (intersectY >= 0) {
2870                 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2871             }
2872 
2873             for (int y = startY; y < endY && !foundCell; y++) {
2874                 inner:
2875                 for (int x = startX; x < endX; x++) {
2876                     for (int i = 0; i < spanX; i++) {
2877                         for (int j = 0; j < spanY; j++) {
2878                             if (occupied[x + i][y + j]) {
2879                                 // small optimization: we can skip to after the column we just found
2880                                 // an occupied cell
2881                                 x += i;
2882                                 continue inner;
2883                             }
2884                         }
2885                     }
2886                     if (cellXY != null) {
2887                         cellXY[0] = x;
2888                         cellXY[1] = y;
2889                     }
2890                     foundCell = true;
2891                     break;
2892                 }
2893             }
2894             if (intersectX == -1 && intersectY == -1) {
2895                 break;
2896             } else {
2897                 // if we failed to find anything, try again but without any requirements of
2898                 // intersecting
2899                 intersectX = -1;
2900                 intersectY = -1;
2901                 continue;
2902             }
2903         }
2904 
2905         // re-mark space taken by ignoreView as occupied
2906         markCellsAsOccupiedForView(ignoreView, occupied);
2907         return foundCell;
2908     }
2909 
2910     /**
2911      * A drag event has begun over this layout.
2912      * It may have begun over this layout (in which case onDragChild is called first),
2913      * or it may have begun on another layout.
2914      */
onDragEnter()2915     void onDragEnter() {
2916         mDragEnforcer.onDragEnter();
2917         mDragging = true;
2918     }
2919 
2920     /**
2921      * Called when drag has left this CellLayout or has been completed (successfully or not)
2922      */
onDragExit()2923     void onDragExit() {
2924         mDragEnforcer.onDragExit();
2925         // This can actually be called when we aren't in a drag, e.g. when adding a new
2926         // item to this layout via the customize drawer.
2927         // Guard against that case.
2928         if (mDragging) {
2929             mDragging = false;
2930         }
2931 
2932         // Invalidate the drag data
2933         mDragCell[0] = mDragCell[1] = -1;
2934         mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2935         mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
2936         revertTempState();
2937         setIsDragOverlapping(false);
2938     }
2939 
2940     /**
2941      * Mark a child as having been dropped.
2942      * At the beginning of the drag operation, the child may have been on another
2943      * screen, but it is re-parented before this method is called.
2944      *
2945      * @param child The child that is being dropped
2946      */
onDropChild(View child)2947     void onDropChild(View child) {
2948         if (child != null) {
2949             LayoutParams lp = (LayoutParams) child.getLayoutParams();
2950             lp.dropped = true;
2951             child.requestLayout();
2952         }
2953     }
2954 
2955     /**
2956      * Computes a bounding rectangle for a range of cells
2957      *
2958      * @param cellX X coordinate of upper left corner expressed as a cell position
2959      * @param cellY Y coordinate of upper left corner expressed as a cell position
2960      * @param cellHSpan Width in cells
2961      * @param cellVSpan Height in cells
2962      * @param resultRect Rect into which to put the results
2963      */
cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect)2964     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
2965         final int cellWidth = mCellWidth;
2966         final int cellHeight = mCellHeight;
2967         final int widthGap = mWidthGap;
2968         final int heightGap = mHeightGap;
2969 
2970         final int hStartPadding = getPaddingLeft();
2971         final int vStartPadding = getPaddingTop();
2972 
2973         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2974         int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2975 
2976         int x = hStartPadding + cellX * (cellWidth + widthGap);
2977         int y = vStartPadding + cellY * (cellHeight + heightGap);
2978 
2979         resultRect.set(x, y, x + width, y + height);
2980     }
2981 
2982     /**
2983      * Computes the required horizontal and vertical cell spans to always
2984      * fit the given rectangle.
2985      *
2986      * @param width Width in pixels
2987      * @param height Height in pixels
2988      * @param result An array of length 2 in which to store the result (may be null).
2989      */
rectToCell(int width, int height, int[] result)2990     public int[] rectToCell(int width, int height, int[] result) {
2991         return rectToCell(getResources(), width, height, result);
2992     }
2993 
rectToCell(Resources resources, int width, int height, int[] result)2994     public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
2995         // Always assume we're working with the smallest span to make sure we
2996         // reserve enough space in both orientations.
2997         int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2998         int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
2999         int smallerSize = Math.min(actualWidth, actualHeight);
3000 
3001         // Always round up to next largest cell
3002         int spanX = (int) Math.ceil(width / (float) smallerSize);
3003         int spanY = (int) Math.ceil(height / (float) smallerSize);
3004 
3005         if (result == null) {
3006             return new int[] { spanX, spanY };
3007         }
3008         result[0] = spanX;
3009         result[1] = spanY;
3010         return result;
3011     }
3012 
cellSpansToSize(int hSpans, int vSpans)3013     public int[] cellSpansToSize(int hSpans, int vSpans) {
3014         int[] size = new int[2];
3015         size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
3016         size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
3017         return size;
3018     }
3019 
3020     /**
3021      * Calculate the grid spans needed to fit given item
3022      */
calculateSpans(ItemInfo info)3023     public void calculateSpans(ItemInfo info) {
3024         final int minWidth;
3025         final int minHeight;
3026 
3027         if (info instanceof LauncherAppWidgetInfo) {
3028             minWidth = ((LauncherAppWidgetInfo) info).minWidth;
3029             minHeight = ((LauncherAppWidgetInfo) info).minHeight;
3030         } else if (info instanceof PendingAddWidgetInfo) {
3031             minWidth = ((PendingAddWidgetInfo) info).info.minWidth;
3032             minHeight = ((PendingAddWidgetInfo) info).info.minHeight;
3033         } else {
3034             // It's not a widget, so it must be 1x1
3035             info.spanX = info.spanY = 1;
3036             return;
3037         }
3038         int[] spans = rectToCell(minWidth, minHeight, null);
3039         info.spanX = spans[0];
3040         info.spanY = spans[1];
3041     }
3042 
3043     /**
3044      * Find the first vacant cell, if there is one.
3045      *
3046      * @param vacant Holds the x and y coordinate of the vacant cell
3047      * @param spanX Horizontal cell span.
3048      * @param spanY Vertical cell span.
3049      *
3050      * @return True if a vacant cell was found
3051      */
getVacantCell(int[] vacant, int spanX, int spanY)3052     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
3053 
3054         return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
3055     }
3056 
findVacantCell(int[] vacant, int spanX, int spanY, int xCount, int yCount, boolean[][] occupied)3057     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
3058             int xCount, int yCount, boolean[][] occupied) {
3059 
3060         for (int y = 0; y < yCount; y++) {
3061             for (int x = 0; x < xCount; x++) {
3062                 boolean available = !occupied[x][y];
3063 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
3064                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
3065                         available = available && !occupied[i][j];
3066                         if (!available) break out;
3067                     }
3068                 }
3069 
3070                 if (available) {
3071                     vacant[0] = x;
3072                     vacant[1] = y;
3073                     return true;
3074                 }
3075             }
3076         }
3077 
3078         return false;
3079     }
3080 
clearOccupiedCells()3081     private void clearOccupiedCells() {
3082         for (int x = 0; x < mCountX; x++) {
3083             for (int y = 0; y < mCountY; y++) {
3084                 mOccupied[x][y] = false;
3085             }
3086         }
3087     }
3088 
onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY)3089     public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
3090         markCellsAsUnoccupiedForView(view);
3091         markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
3092     }
3093 
markCellsAsOccupiedForView(View view)3094     public void markCellsAsOccupiedForView(View view) {
3095         markCellsAsOccupiedForView(view, mOccupied);
3096     }
markCellsAsOccupiedForView(View view, boolean[][] occupied)3097     public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
3098         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
3099         LayoutParams lp = (LayoutParams) view.getLayoutParams();
3100         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
3101     }
3102 
markCellsAsUnoccupiedForView(View view)3103     public void markCellsAsUnoccupiedForView(View view) {
3104         markCellsAsUnoccupiedForView(view, mOccupied);
3105     }
markCellsAsUnoccupiedForView(View view, boolean occupied[][])3106     public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
3107         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
3108         LayoutParams lp = (LayoutParams) view.getLayoutParams();
3109         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
3110     }
3111 
markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied, boolean value)3112     private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
3113             boolean value) {
3114         if (cellX < 0 || cellY < 0) return;
3115         for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
3116             for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
3117                 occupied[x][y] = value;
3118             }
3119         }
3120     }
3121 
getDesiredWidth()3122     public int getDesiredWidth() {
3123         return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
3124                 (Math.max((mCountX - 1), 0) * mWidthGap);
3125     }
3126 
getDesiredHeight()3127     public int getDesiredHeight()  {
3128         return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
3129                 (Math.max((mCountY - 1), 0) * mHeightGap);
3130     }
3131 
isOccupied(int x, int y)3132     public boolean isOccupied(int x, int y) {
3133         if (x < mCountX && y < mCountY) {
3134             return mOccupied[x][y];
3135         } else {
3136             throw new RuntimeException("Position exceeds the bound of this CellLayout");
3137         }
3138     }
3139 
3140     @Override
generateLayoutParams(AttributeSet attrs)3141     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3142         return new CellLayout.LayoutParams(getContext(), attrs);
3143     }
3144 
3145     @Override
checkLayoutParams(ViewGroup.LayoutParams p)3146     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3147         return p instanceof CellLayout.LayoutParams;
3148     }
3149 
3150     @Override
generateLayoutParams(ViewGroup.LayoutParams p)3151     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3152         return new CellLayout.LayoutParams(p);
3153     }
3154 
3155     public static class CellLayoutAnimationController extends LayoutAnimationController {
CellLayoutAnimationController(Animation animation, float delay)3156         public CellLayoutAnimationController(Animation animation, float delay) {
3157             super(animation, delay);
3158         }
3159 
3160         @Override
getDelayForView(View view)3161         protected long getDelayForView(View view) {
3162             return (int) (Math.random() * 150);
3163         }
3164     }
3165 
3166     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
3167         /**
3168          * Horizontal location of the item in the grid.
3169          */
3170         @ViewDebug.ExportedProperty
3171         public int cellX;
3172 
3173         /**
3174          * Vertical location of the item in the grid.
3175          */
3176         @ViewDebug.ExportedProperty
3177         public int cellY;
3178 
3179         /**
3180          * Temporary horizontal location of the item in the grid during reorder
3181          */
3182         public int tmpCellX;
3183 
3184         /**
3185          * Temporary vertical location of the item in the grid during reorder
3186          */
3187         public int tmpCellY;
3188 
3189         /**
3190          * Indicates that the temporary coordinates should be used to layout the items
3191          */
3192         public boolean useTmpCoords;
3193 
3194         /**
3195          * Number of cells spanned horizontally by the item.
3196          */
3197         @ViewDebug.ExportedProperty
3198         public int cellHSpan;
3199 
3200         /**
3201          * Number of cells spanned vertically by the item.
3202          */
3203         @ViewDebug.ExportedProperty
3204         public int cellVSpan;
3205 
3206         /**
3207          * Indicates whether the item will set its x, y, width and height parameters freely,
3208          * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
3209          */
3210         public boolean isLockedToGrid = true;
3211 
3212         /**
3213          * Indicates whether this item can be reordered. Always true except in the case of the
3214          * the AllApps button.
3215          */
3216         public boolean canReorder = true;
3217 
3218         // X coordinate of the view in the layout.
3219         @ViewDebug.ExportedProperty
3220         int x;
3221         // Y coordinate of the view in the layout.
3222         @ViewDebug.ExportedProperty
3223         int y;
3224 
3225         boolean dropped;
3226 
LayoutParams(Context c, AttributeSet attrs)3227         public LayoutParams(Context c, AttributeSet attrs) {
3228             super(c, attrs);
3229             cellHSpan = 1;
3230             cellVSpan = 1;
3231         }
3232 
LayoutParams(ViewGroup.LayoutParams source)3233         public LayoutParams(ViewGroup.LayoutParams source) {
3234             super(source);
3235             cellHSpan = 1;
3236             cellVSpan = 1;
3237         }
3238 
LayoutParams(LayoutParams source)3239         public LayoutParams(LayoutParams source) {
3240             super(source);
3241             this.cellX = source.cellX;
3242             this.cellY = source.cellY;
3243             this.cellHSpan = source.cellHSpan;
3244             this.cellVSpan = source.cellVSpan;
3245         }
3246 
LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan)3247         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
3248             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
3249             this.cellX = cellX;
3250             this.cellY = cellY;
3251             this.cellHSpan = cellHSpan;
3252             this.cellVSpan = cellVSpan;
3253         }
3254 
setup(int cellWidth, int cellHeight, int widthGap, int heightGap, boolean invertHorizontally, int colCount)3255         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
3256                 boolean invertHorizontally, int colCount) {
3257             if (isLockedToGrid) {
3258                 final int myCellHSpan = cellHSpan;
3259                 final int myCellVSpan = cellVSpan;
3260                 int myCellX = useTmpCoords ? tmpCellX : cellX;
3261                 int myCellY = useTmpCoords ? tmpCellY : cellY;
3262 
3263                 if (invertHorizontally) {
3264                     myCellX = colCount - myCellX - cellHSpan;
3265                 }
3266 
3267                 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3268                         leftMargin - rightMargin;
3269                 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3270                         topMargin - bottomMargin;
3271                 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3272                 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
3273             }
3274         }
3275 
toString()3276         public String toString() {
3277             return "(" + this.cellX + ", " + this.cellY + ")";
3278         }
3279 
setWidth(int width)3280         public void setWidth(int width) {
3281             this.width = width;
3282         }
3283 
getWidth()3284         public int getWidth() {
3285             return width;
3286         }
3287 
setHeight(int height)3288         public void setHeight(int height) {
3289             this.height = height;
3290         }
3291 
getHeight()3292         public int getHeight() {
3293             return height;
3294         }
3295 
setX(int x)3296         public void setX(int x) {
3297             this.x = x;
3298         }
3299 
getX()3300         public int getX() {
3301             return x;
3302         }
3303 
setY(int y)3304         public void setY(int y) {
3305             this.y = y;
3306         }
3307 
getY()3308         public int getY() {
3309             return y;
3310         }
3311     }
3312 
3313     // This class stores info for two purposes:
3314     // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3315     //    its spanX, spanY, and the screen it is on
3316     // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3317     //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3318     //    the CellLayout that was long clicked
3319     static final class CellInfo {
3320         View cell;
3321         int cellX = -1;
3322         int cellY = -1;
3323         int spanX;
3324         int spanY;
3325         int screen;
3326         long container;
3327 
3328         @Override
toString()3329         public String toString() {
3330             return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3331                     + ", x=" + cellX + ", y=" + cellY + "]";
3332         }
3333     }
3334 
lastDownOnOccupiedCell()3335     public boolean lastDownOnOccupiedCell() {
3336         return mLastDownOnOccupiedCell;
3337     }
3338 }
3339