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