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