• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher2;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.TimeInterpolator;
25 import android.animation.ValueAnimator;
26 import android.animation.ValueAnimator.AnimatorUpdateListener;
27 import android.content.Context;
28 import android.content.res.Resources;
29 import android.content.res.TypedArray;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.Paint;
33 import android.graphics.Point;
34 import android.graphics.PointF;
35 import android.graphics.PorterDuff;
36 import android.graphics.PorterDuffXfermode;
37 import android.graphics.Rect;
38 import android.graphics.RectF;
39 import android.graphics.Region;
40 import android.graphics.drawable.Drawable;
41 import android.graphics.drawable.NinePatchDrawable;
42 import android.util.AttributeSet;
43 import android.util.Log;
44 import android.view.MotionEvent;
45 import android.view.View;
46 import android.view.ViewDebug;
47 import android.view.ViewGroup;
48 import android.view.animation.Animation;
49 import android.view.animation.DecelerateInterpolator;
50 import android.view.animation.LayoutAnimationController;
51 
52 import com.android.launcher.R;
53 import com.android.launcher2.FolderIcon.FolderRingAnimator;
54 
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.HashMap;
58 
59 public class CellLayout extends ViewGroup {
60     static final String TAG = "CellLayout";
61 
62     private int mOriginalCellWidth;
63     private int mOriginalCellHeight;
64     private int mCellWidth;
65     private int mCellHeight;
66 
67     private int mCountX;
68     private int mCountY;
69 
70     private int mOriginalWidthGap;
71     private int mOriginalHeightGap;
72     private int mWidthGap;
73     private int mHeightGap;
74     private int mMaxGap;
75 
76     private final Rect mRect = new Rect();
77     private final CellInfo mCellInfo = new CellInfo();
78 
79     // These are temporary variables to prevent having to allocate a new object just to
80     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
81     private final int[] mTmpXY = new int[2];
82     private final int[] mTmpPoint = new int[2];
83     private final PointF mTmpPointF = new PointF();
84     int[] mTempLocation = new int[2];
85 
86     boolean[][] mOccupied;
87     private boolean mLastDownOnOccupiedCell = false;
88 
89     private OnTouchListener mInterceptTouchListener;
90 
91     private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
92     private int[] mFolderLeaveBehindCell = {-1, -1};
93 
94     private int mForegroundAlpha = 0;
95     private float mBackgroundAlpha;
96     private float mBackgroundAlphaMultiplier = 1.0f;
97 
98     private Drawable mNormalBackground;
99     private Drawable mActiveBackground;
100     private Drawable mActiveGlowBackground;
101     private Drawable mNormalBackgroundMini;
102     private Drawable mNormalGlowBackgroundMini;
103     private Drawable mActiveBackgroundMini;
104     private Drawable mActiveGlowBackgroundMini;
105     private Drawable mOverScrollForegroundDrawable;
106     private Drawable mOverScrollLeft;
107     private Drawable mOverScrollRight;
108     private Rect mBackgroundRect;
109     private Rect mForegroundRect;
110     private Rect mGlowBackgroundRect;
111     private float mGlowBackgroundScale;
112     private float mGlowBackgroundAlpha;
113     private int mForegroundPadding;
114 
115     private boolean mAcceptsDrops = true;
116     // If we're actively dragging something over this screen, mIsDragOverlapping is true
117     private boolean mIsDragOverlapping = false;
118     private boolean mIsDragOccuring = false;
119     private boolean mIsDefaultDropTarget = false;
120     private final Point mDragCenter = new Point();
121 
122     // These arrays are used to implement the drag visualization on x-large screens.
123     // They are used as circular arrays, indexed by mDragOutlineCurrent.
124     private Point[] mDragOutlines = new Point[4];
125     private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
126     private InterruptibleInOutAnimator[] mDragOutlineAnims =
127             new InterruptibleInOutAnimator[mDragOutlines.length];
128 
129     // Used as an index into the above 3 arrays; indicates which is the most current value.
130     private int mDragOutlineCurrent = 0;
131     private final Paint mDragOutlinePaint = new Paint();
132 
133     private BubbleTextView mPressedOrFocusedIcon;
134 
135     private Drawable mCrosshairsDrawable = null;
136     private InterruptibleInOutAnimator mCrosshairsAnimator = null;
137     private float mCrosshairsVisibility = 0.0f;
138 
139     private HashMap<CellLayout.LayoutParams, ObjectAnimator> mReorderAnimators = new
140             HashMap<CellLayout.LayoutParams, ObjectAnimator>();
141 
142     // When a drag operation is in progress, holds the nearest cell to the touch point
143     private final int[] mDragCell = new int[2];
144 
145     private boolean mDragging = false;
146 
147     private TimeInterpolator mEaseOutInterpolator;
148     private CellLayoutChildren mChildren;
149 
CellLayout(Context context)150     public CellLayout(Context context) {
151         this(context, null);
152     }
153 
CellLayout(Context context, AttributeSet attrs)154     public CellLayout(Context context, AttributeSet attrs) {
155         this(context, attrs, 0);
156     }
157 
CellLayout(Context context, AttributeSet attrs, int defStyle)158     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
159         super(context, attrs, defStyle);
160 
161         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
162         // the user where a dragged item will land when dropped.
163         setWillNotDraw(false);
164 
165         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
166 
167         mOriginalCellWidth =
168             mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
169         mOriginalCellHeight =
170             mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
171         mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
172         mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
173         mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
174         mCountX = LauncherModel.getCellCountX();
175         mCountY = LauncherModel.getCellCountY();
176         mOccupied = new boolean[mCountX][mCountY];
177 
178         a.recycle();
179 
180         setAlwaysDrawnWithCacheEnabled(false);
181 
182         final Resources res = getResources();
183 
184         mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
185         mActiveBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
186         mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
187 
188         mNormalBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue);
189         mNormalGlowBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue_strong);
190         mActiveBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue_strong);
191         mActiveGlowBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue_strong);
192         mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
193         mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
194         mForegroundPadding =
195                 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
196 
197         mNormalBackground.setFilterBitmap(true);
198         mActiveBackground.setFilterBitmap(true);
199         mActiveGlowBackground.setFilterBitmap(true);
200         mNormalBackgroundMini.setFilterBitmap(true);
201         mNormalGlowBackgroundMini.setFilterBitmap(true);
202         mActiveBackgroundMini.setFilterBitmap(true);
203         mActiveGlowBackgroundMini.setFilterBitmap(true);
204 
205         // Initialize the data structures used for the drag visualization.
206 
207         mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
208         mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
209 
210         // Set up the animation for fading the crosshairs in and out
211         int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime);
212         mCrosshairsAnimator = new InterruptibleInOutAnimator(animDuration, 0.0f, 1.0f);
213         mCrosshairsAnimator.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
214             public void onAnimationUpdate(ValueAnimator animation) {
215                 mCrosshairsVisibility = ((Float) animation.getAnimatedValue()).floatValue();
216                 invalidate();
217             }
218         });
219         mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator);
220 
221         mDragCell[0] = mDragCell[1] = -1;
222         for (int i = 0; i < mDragOutlines.length; i++) {
223             mDragOutlines[i] = new Point(-1, -1);
224         }
225 
226         // When dragging things around the home screens, we show a green outline of
227         // where the item will land. The outlines gradually fade out, leaving a trail
228         // behind the drag path.
229         // Set up all the animations that are used to implement this fading.
230         final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
231         final float fromAlphaValue = 0;
232         final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
233 
234         Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
235 
236         for (int i = 0; i < mDragOutlineAnims.length; i++) {
237             final InterruptibleInOutAnimator anim =
238                 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
239             anim.getAnimator().setInterpolator(mEaseOutInterpolator);
240             final int thisIndex = i;
241             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
242                 public void onAnimationUpdate(ValueAnimator animation) {
243                     final Bitmap outline = (Bitmap)anim.getTag();
244 
245                     // If an animation is started and then stopped very quickly, we can still
246                     // get spurious updates we've cleared the tag. Guard against this.
247                     if (outline == null) {
248                         if (false) {
249                             Object val = animation.getAnimatedValue();
250                             Log.d(TAG, "anim " + thisIndex + " update: " + val +
251                                      ", isStopped " + anim.isStopped());
252                         }
253                         // Try to prevent it from continuing to run
254                         animation.cancel();
255                     } else {
256                         mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
257                         final int left = mDragOutlines[thisIndex].x;
258                         final int top = mDragOutlines[thisIndex].y;
259                         CellLayout.this.invalidate(left, top,
260                                 left + outline.getWidth(), top + outline.getHeight());
261                     }
262                 }
263             });
264             // The animation holds a reference to the drag outline bitmap as long is it's
265             // running. This way the bitmap can be GCed when the animations are complete.
266             anim.getAnimator().addListener(new AnimatorListenerAdapter() {
267                 @Override
268                 public void onAnimationEnd(Animator animation) {
269                     if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
270                         anim.setTag(null);
271                     }
272                 }
273             });
274             mDragOutlineAnims[i] = anim;
275         }
276 
277         mBackgroundRect = new Rect();
278         mForegroundRect = new Rect();
279         mGlowBackgroundRect = new Rect();
280         setHoverScale(1.0f);
281         setHoverAlpha(1.0f);
282 
283         mChildren = new CellLayoutChildren(context);
284         mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
285         addView(mChildren);
286     }
287 
widthInPortrait(Resources r, int numCells)288     static int widthInPortrait(Resources r, int numCells) {
289         // We use this method from Workspace to figure out how many rows/columns Launcher should
290         // have. We ignore the left/right padding on CellLayout because it turns out in our design
291         // the padding extends outside the visible screen size, but it looked fine anyway.
292         int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
293         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
294                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
295 
296         return  minGap * (numCells - 1) + cellWidth * numCells;
297     }
298 
heightInLandscape(Resources r, int numCells)299     static int heightInLandscape(Resources r, int numCells) {
300         // We use this method from Workspace to figure out how many rows/columns Launcher should
301         // have. We ignore the left/right padding on CellLayout because it turns out in our design
302         // the padding extends outside the visible screen size, but it looked fine anyway.
303         int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
304         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
305                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
306 
307         return minGap * (numCells - 1) + cellHeight * numCells;
308     }
309 
enableHardwareLayers()310     public void enableHardwareLayers() {
311         mChildren.enableHardwareLayers();
312     }
313 
setGridSize(int x, int y)314     public void setGridSize(int x, int y) {
315         mCountX = x;
316         mCountY = y;
317         mOccupied = new boolean[mCountX][mCountY];
318         requestLayout();
319     }
320 
invalidateBubbleTextView(BubbleTextView icon)321     private void invalidateBubbleTextView(BubbleTextView icon) {
322         final int padding = icon.getPressedOrFocusedBackgroundPadding();
323         invalidate(icon.getLeft() + getPaddingLeft() - padding,
324                 icon.getTop() + getPaddingTop() - padding,
325                 icon.getRight() + getPaddingLeft() + padding,
326                 icon.getBottom() + getPaddingTop() + padding);
327     }
328 
setOverScrollAmount(float r, boolean left)329     void setOverScrollAmount(float r, boolean left) {
330         if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
331             mOverScrollForegroundDrawable = mOverScrollLeft;
332         } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
333             mOverScrollForegroundDrawable = mOverScrollRight;
334         }
335 
336         mForegroundAlpha = (int) Math.round((r * 255));
337         mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
338         invalidate();
339     }
340 
setPressedOrFocusedIcon(BubbleTextView icon)341     void setPressedOrFocusedIcon(BubbleTextView icon) {
342         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
343         // requires an expanded clip rect (due to the glow's blur radius)
344         BubbleTextView oldIcon = mPressedOrFocusedIcon;
345         mPressedOrFocusedIcon = icon;
346         if (oldIcon != null) {
347             invalidateBubbleTextView(oldIcon);
348         }
349         if (mPressedOrFocusedIcon != null) {
350             invalidateBubbleTextView(mPressedOrFocusedIcon);
351         }
352     }
353 
getChildrenLayout()354     public CellLayoutChildren getChildrenLayout() {
355         if (getChildCount() > 0) {
356             return (CellLayoutChildren) getChildAt(0);
357         }
358         return null;
359     }
360 
setIsDefaultDropTarget(boolean isDefaultDropTarget)361     public void setIsDefaultDropTarget(boolean isDefaultDropTarget) {
362         if (mIsDefaultDropTarget != isDefaultDropTarget) {
363             mIsDefaultDropTarget = isDefaultDropTarget;
364             invalidate();
365         }
366     }
367 
setIsDragOccuring(boolean isDragOccuring)368     void setIsDragOccuring(boolean isDragOccuring) {
369         if (mIsDragOccuring != isDragOccuring) {
370             mIsDragOccuring = isDragOccuring;
371             invalidate();
372         }
373     }
374 
setIsDragOverlapping(boolean isDragOverlapping)375     void setIsDragOverlapping(boolean isDragOverlapping) {
376         if (mIsDragOverlapping != isDragOverlapping) {
377             mIsDragOverlapping = isDragOverlapping;
378             invalidate();
379         }
380     }
381 
getIsDragOverlapping()382     boolean getIsDragOverlapping() {
383         return mIsDragOverlapping;
384     }
385 
updateGlowRect()386     private void updateGlowRect() {
387         float marginFraction = (mGlowBackgroundScale - 1.0f) / 2.0f;
388         int marginX = (int) (marginFraction * (mBackgroundRect.right - mBackgroundRect.left));
389         int marginY = (int) (marginFraction * (mBackgroundRect.bottom - mBackgroundRect.top));
390         mGlowBackgroundRect.set(mBackgroundRect.left - marginX, mBackgroundRect.top - marginY,
391                 mBackgroundRect.right + marginX, mBackgroundRect.bottom + marginY);
392         invalidate();
393     }
394 
setHoverScale(float scaleFactor)395     public void setHoverScale(float scaleFactor) {
396         if (scaleFactor != mGlowBackgroundScale) {
397             mGlowBackgroundScale = scaleFactor;
398             updateGlowRect();
399             if (getParent() != null) {
400                 ((View) getParent()).invalidate();
401             }
402         }
403     }
404 
getHoverScale()405     public float getHoverScale() {
406         return mGlowBackgroundScale;
407     }
408 
getHoverAlpha()409     public float getHoverAlpha() {
410         return mGlowBackgroundAlpha;
411     }
412 
setHoverAlpha(float alpha)413     public void setHoverAlpha(float alpha) {
414         mGlowBackgroundAlpha = alpha;
415         invalidate();
416     }
417 
animateDrop()418     void animateDrop() {
419         Resources res = getResources();
420         float onDropScale = res.getInteger(R.integer.config_screenOnDropScalePercent) / 100.0f;
421         ObjectAnimator scaleUp = ObjectAnimator.ofFloat(this, "hoverScale", onDropScale);
422         scaleUp.setDuration(res.getInteger(R.integer.config_screenOnDropScaleUpDuration));
423         ObjectAnimator scaleDown = ObjectAnimator.ofFloat(this, "hoverScale", 1.0f);
424         scaleDown.setDuration(res.getInteger(R.integer.config_screenOnDropScaleDownDuration));
425         ObjectAnimator alphaFadeOut = ObjectAnimator.ofFloat(this, "hoverAlpha", 0.0f);
426 
427         alphaFadeOut.setStartDelay(res.getInteger(R.integer.config_screenOnDropAlphaFadeDelay));
428         alphaFadeOut.setDuration(res.getInteger(R.integer.config_screenOnDropAlphaFadeDuration));
429 
430         AnimatorSet bouncer = new AnimatorSet();
431         bouncer.play(scaleUp).before(scaleDown);
432         bouncer.play(scaleUp).with(alphaFadeOut);
433         bouncer.addListener(new AnimatorListenerAdapter() {
434             @Override
435             public void onAnimationStart(Animator animation) {
436                 setIsDragOverlapping(true);
437             }
438             @Override
439             public void onAnimationEnd(Animator animation) {
440                 setIsDragOverlapping(false);
441                 setHoverScale(1.0f);
442                 setHoverAlpha(1.0f);
443             }
444         });
445         bouncer.start();
446     }
447 
448     @Override
onDraw(Canvas canvas)449     protected void onDraw(Canvas canvas) {
450         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
451         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
452         // When we're small, we are either drawn normally or in the "accepts drops" state (during
453         // a drag). However, we also drag the mini hover background *over* one of those two
454         // backgrounds
455         if (mBackgroundAlpha > 0.0f) {
456             Drawable bg;
457             boolean mini = getScaleX() < 0.5f;
458 
459             if (mIsDragOverlapping) {
460                 // In the mini case, we draw the active_glow bg *over* the active background
461                 bg = mini ? mActiveBackgroundMini : mActiveGlowBackground;
462             } else if (mIsDragOccuring && mAcceptsDrops) {
463                 bg = mini ? mActiveBackgroundMini : mActiveBackground;
464             } else if (mIsDefaultDropTarget && mini) {
465                 bg = mNormalGlowBackgroundMini;
466             } else {
467                 bg = mini ? mNormalBackgroundMini : mNormalBackground;
468             }
469 
470             bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
471             bg.setBounds(mBackgroundRect);
472             bg.draw(canvas);
473 
474             if (mini && mIsDragOverlapping) {
475                 boolean modifiedClipRect = false;
476                 if (mGlowBackgroundScale > 1.0f) {
477                     // If the hover background's scale is greater than 1, we'll be drawing outside
478                     // the bounds of this CellLayout. Get around that by temporarily increasing the
479                     // size of the clip rect
480                     float marginFraction = (mGlowBackgroundScale - 1.0f) / 2.0f;
481                     Rect clipRect = canvas.getClipBounds();
482                     int marginX = (int) (marginFraction * (clipRect.right - clipRect.left));
483                     int marginY = (int) (marginFraction * (clipRect.bottom - clipRect.top));
484                     canvas.save(Canvas.CLIP_SAVE_FLAG);
485                     canvas.clipRect(-marginX, -marginY,
486                             getWidth() + marginX, getHeight() + marginY, Region.Op.REPLACE);
487                     modifiedClipRect = true;
488                 }
489 
490                 mActiveGlowBackgroundMini.setAlpha(
491                         (int) (mBackgroundAlpha * mGlowBackgroundAlpha * 255));
492                 mActiveGlowBackgroundMini.setBounds(mGlowBackgroundRect);
493                 mActiveGlowBackgroundMini.draw(canvas);
494                 if (modifiedClipRect) {
495                     canvas.restore();
496                 }
497             }
498         }
499 
500         if (mCrosshairsVisibility > 0.0f) {
501             final int countX = mCountX;
502             final int countY = mCountY;
503 
504             final float MAX_ALPHA = 0.4f;
505             final int MAX_VISIBLE_DISTANCE = 600;
506             final float DISTANCE_MULTIPLIER = 0.002f;
507 
508             final Drawable d = mCrosshairsDrawable;
509             final int width = d.getIntrinsicWidth();
510             final int height = d.getIntrinsicHeight();
511 
512             int x = getPaddingLeft() - (mWidthGap / 2) - (width / 2);
513             for (int col = 0; col <= countX; col++) {
514                 int y = getPaddingTop() - (mHeightGap / 2) - (height / 2);
515                 for (int row = 0; row <= countY; row++) {
516                     mTmpPointF.set(x - mDragCenter.x, y - mDragCenter.y);
517                     float dist = mTmpPointF.length();
518                     // Crosshairs further from the drag point are more faint
519                     float alpha = Math.min(MAX_ALPHA,
520                             DISTANCE_MULTIPLIER * (MAX_VISIBLE_DISTANCE - dist));
521                     if (alpha > 0.0f) {
522                         d.setBounds(x, y, x + width, y + height);
523                         d.setAlpha((int) (alpha * 255 * mCrosshairsVisibility));
524                         d.draw(canvas);
525                     }
526                     y += mCellHeight + mHeightGap;
527                 }
528                 x += mCellWidth + mWidthGap;
529             }
530         }
531 
532         final Paint paint = mDragOutlinePaint;
533         for (int i = 0; i < mDragOutlines.length; i++) {
534             final float alpha = mDragOutlineAlphas[i];
535             if (alpha > 0) {
536                 final Point p = mDragOutlines[i];
537                 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
538                 paint.setAlpha((int)(alpha + .5f));
539                 canvas.drawBitmap(b, p.x, p.y, paint);
540             }
541         }
542 
543         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
544         // requires an expanded clip rect (due to the glow's blur radius)
545         if (mPressedOrFocusedIcon != null) {
546             final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
547             final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
548             if (b != null) {
549                 canvas.drawBitmap(b,
550                         mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
551                         mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
552                         null);
553             }
554         }
555 
556         // The folder outer / inner ring image(s)
557         for (int i = 0; i < mFolderOuterRings.size(); i++) {
558             FolderRingAnimator fra = mFolderOuterRings.get(i);
559 
560             // Draw outer ring
561             Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
562             int width = (int) fra.getOuterRingSize();
563             int height = width;
564             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
565 
566             int centerX = mTempLocation[0] + mCellWidth / 2;
567             int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
568 
569             canvas.save();
570             canvas.translate(centerX - width / 2, centerY - height / 2);
571             d.setBounds(0, 0, width, height);
572             d.draw(canvas);
573             canvas.restore();
574 
575             // Draw inner ring
576             d = FolderRingAnimator.sSharedInnerRingDrawable;
577             width = (int) fra.getInnerRingSize();
578             height = width;
579             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
580 
581             centerX = mTempLocation[0] + mCellWidth / 2;
582             centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
583             canvas.save();
584             canvas.translate(centerX - width / 2, centerY - width / 2);
585             d.setBounds(0, 0, width, height);
586             d.draw(canvas);
587             canvas.restore();
588         }
589 
590         if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
591             Drawable d = FolderIcon.sSharedFolderLeaveBehind;
592             int width = d.getIntrinsicWidth();
593             int height = d.getIntrinsicHeight();
594 
595             cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
596             int centerX = mTempLocation[0] + mCellWidth / 2;
597             int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
598 
599             canvas.save();
600             canvas.translate(centerX - width / 2, centerY - width / 2);
601             d.setBounds(0, 0, width, height);
602             d.draw(canvas);
603             canvas.restore();
604         }
605     }
606 
607     @Override
dispatchDraw(Canvas canvas)608     protected void dispatchDraw(Canvas canvas) {
609         super.dispatchDraw(canvas);
610         if (mForegroundAlpha > 0) {
611             mOverScrollForegroundDrawable.setBounds(mForegroundRect);
612             Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
613             p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
614             mOverScrollForegroundDrawable.draw(canvas);
615             p.setXfermode(null);
616         }
617     }
618 
showFolderAccept(FolderRingAnimator fra)619     public void showFolderAccept(FolderRingAnimator fra) {
620         mFolderOuterRings.add(fra);
621     }
622 
hideFolderAccept(FolderRingAnimator fra)623     public void hideFolderAccept(FolderRingAnimator fra) {
624         if (mFolderOuterRings.contains(fra)) {
625             mFolderOuterRings.remove(fra);
626         }
627         invalidate();
628     }
629 
setFolderLeaveBehindCell(int x, int y)630     public void setFolderLeaveBehindCell(int x, int y) {
631         mFolderLeaveBehindCell[0] = x;
632         mFolderLeaveBehindCell[1] = y;
633         invalidate();
634     }
635 
clearFolderLeaveBehind()636     public void clearFolderLeaveBehind() {
637         mFolderLeaveBehindCell[0] = -1;
638         mFolderLeaveBehindCell[1] = -1;
639         invalidate();
640     }
641 
642     @Override
shouldDelayChildPressedState()643     public boolean shouldDelayChildPressedState() {
644         return false;
645     }
646 
647     @Override
cancelLongPress()648     public void cancelLongPress() {
649         super.cancelLongPress();
650 
651         // Cancel long press for all children
652         final int count = getChildCount();
653         for (int i = 0; i < count; i++) {
654             final View child = getChildAt(i);
655             child.cancelLongPress();
656         }
657     }
658 
setOnInterceptTouchListener(View.OnTouchListener listener)659     public void setOnInterceptTouchListener(View.OnTouchListener listener) {
660         mInterceptTouchListener = listener;
661     }
662 
getCountX()663     int getCountX() {
664         return mCountX;
665     }
666 
getCountY()667     int getCountY() {
668         return mCountY;
669     }
670 
addViewToCellLayout( View child, int index, int childId, LayoutParams params, boolean markCells)671     public boolean addViewToCellLayout(
672             View child, int index, int childId, LayoutParams params, boolean markCells) {
673         final LayoutParams lp = params;
674 
675         // Generate an id for each view, this assumes we have at most 256x256 cells
676         // per workspace screen
677         if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
678             // If the horizontal or vertical span is set to -1, it is taken to
679             // mean that it spans the extent of the CellLayout
680             if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
681             if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
682 
683             child.setId(childId);
684 
685             mChildren.addView(child, index, lp);
686 
687             if (markCells) markCellsAsOccupiedForView(child);
688 
689             return true;
690         }
691         return false;
692     }
693 
setAcceptsDrops(boolean acceptsDrops)694     public void setAcceptsDrops(boolean acceptsDrops) {
695         if (mAcceptsDrops != acceptsDrops) {
696             mAcceptsDrops = acceptsDrops;
697             invalidate();
698         }
699     }
700 
701     @Override
removeAllViews()702     public void removeAllViews() {
703         clearOccupiedCells();
704         mChildren.removeAllViews();
705     }
706 
707     @Override
removeAllViewsInLayout()708     public void removeAllViewsInLayout() {
709         if (mChildren.getChildCount() > 0) {
710             clearOccupiedCells();
711             mChildren.removeAllViewsInLayout();
712         }
713     }
714 
removeViewWithoutMarkingCells(View view)715     public void removeViewWithoutMarkingCells(View view) {
716         mChildren.removeView(view);
717     }
718 
719     @Override
removeView(View view)720     public void removeView(View view) {
721         markCellsAsUnoccupiedForView(view);
722         mChildren.removeView(view);
723     }
724 
725     @Override
removeViewAt(int index)726     public void removeViewAt(int index) {
727         markCellsAsUnoccupiedForView(mChildren.getChildAt(index));
728         mChildren.removeViewAt(index);
729     }
730 
731     @Override
removeViewInLayout(View view)732     public void removeViewInLayout(View view) {
733         markCellsAsUnoccupiedForView(view);
734         mChildren.removeViewInLayout(view);
735     }
736 
737     @Override
removeViews(int start, int count)738     public void removeViews(int start, int count) {
739         for (int i = start; i < start + count; i++) {
740             markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
741         }
742         mChildren.removeViews(start, count);
743     }
744 
745     @Override
removeViewsInLayout(int start, int count)746     public void removeViewsInLayout(int start, int count) {
747         for (int i = start; i < start + count; i++) {
748             markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
749         }
750         mChildren.removeViewsInLayout(start, count);
751     }
752 
drawChildren(Canvas canvas)753     public void drawChildren(Canvas canvas) {
754         mChildren.draw(canvas);
755     }
756 
buildChildrenLayer()757     void buildChildrenLayer() {
758         mChildren.buildLayer();
759     }
760 
761     @Override
onAttachedToWindow()762     protected void onAttachedToWindow() {
763         super.onAttachedToWindow();
764         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
765     }
766 
setTagToCellInfoForPoint(int touchX, int touchY)767     public void setTagToCellInfoForPoint(int touchX, int touchY) {
768         final CellInfo cellInfo = mCellInfo;
769         final Rect frame = mRect;
770         final int x = touchX + mScrollX;
771         final int y = touchY + mScrollY;
772         final int count = mChildren.getChildCount();
773 
774         boolean found = false;
775         for (int i = count - 1; i >= 0; i--) {
776             final View child = mChildren.getChildAt(i);
777             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
778 
779             if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
780                     lp.isLockedToGrid) {
781                 child.getHitRect(frame);
782 
783                 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
784                 // offset that by this CellLayout's padding to test an (x,y) point that is relative
785                 // to this view.
786                 frame.offset(mPaddingLeft, mPaddingTop);
787 
788                 if (frame.contains(x, y)) {
789                     cellInfo.cell = child;
790                     cellInfo.cellX = lp.cellX;
791                     cellInfo.cellY = lp.cellY;
792                     cellInfo.spanX = lp.cellHSpan;
793                     cellInfo.spanY = lp.cellVSpan;
794                     found = true;
795                     break;
796                 }
797             }
798         }
799 
800         mLastDownOnOccupiedCell = found;
801 
802         if (!found) {
803             final int cellXY[] = mTmpXY;
804             pointToCellExact(x, y, cellXY);
805 
806             cellInfo.cell = null;
807             cellInfo.cellX = cellXY[0];
808             cellInfo.cellY = cellXY[1];
809             cellInfo.spanX = 1;
810             cellInfo.spanY = 1;
811         }
812         setTag(cellInfo);
813     }
814 
815     @Override
onInterceptTouchEvent(MotionEvent ev)816     public boolean onInterceptTouchEvent(MotionEvent ev) {
817         // First we clear the tag to ensure that on every touch down we start with a fresh slate,
818         // even in the case where we return early. Not clearing here was causing bugs whereby on
819         // long-press we'd end up picking up an item from a previous drag operation.
820         final int action = ev.getAction();
821 
822         if (action == MotionEvent.ACTION_DOWN) {
823             clearTagCellInfo();
824         }
825 
826         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
827             return true;
828         }
829 
830         if (action == MotionEvent.ACTION_DOWN) {
831             setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
832         }
833         return false;
834     }
835 
clearTagCellInfo()836     private void clearTagCellInfo() {
837         final CellInfo cellInfo = mCellInfo;
838         cellInfo.cell = null;
839         cellInfo.cellX = -1;
840         cellInfo.cellY = -1;
841         cellInfo.spanX = 0;
842         cellInfo.spanY = 0;
843         setTag(cellInfo);
844     }
845 
getTag()846     public CellInfo getTag() {
847         return (CellInfo) super.getTag();
848     }
849 
850     /**
851      * Given a point, return the cell that strictly encloses that point
852      * @param x X coordinate of the point
853      * @param y Y coordinate of the point
854      * @param result Array of 2 ints to hold the x and y coordinate of the cell
855      */
pointToCellExact(int x, int y, int[] result)856     void pointToCellExact(int x, int y, int[] result) {
857         final int hStartPadding = getPaddingLeft();
858         final int vStartPadding = getPaddingTop();
859 
860         result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
861         result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
862 
863         final int xAxis = mCountX;
864         final int yAxis = mCountY;
865 
866         if (result[0] < 0) result[0] = 0;
867         if (result[0] >= xAxis) result[0] = xAxis - 1;
868         if (result[1] < 0) result[1] = 0;
869         if (result[1] >= yAxis) result[1] = yAxis - 1;
870     }
871 
872     /**
873      * Given a point, return the cell that most closely encloses that point
874      * @param x X coordinate of the point
875      * @param y Y coordinate of the point
876      * @param result Array of 2 ints to hold the x and y coordinate of the cell
877      */
pointToCellRounded(int x, int y, int[] result)878     void pointToCellRounded(int x, int y, int[] result) {
879         pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
880     }
881 
882     /**
883      * Given a cell coordinate, return the point that represents the upper left corner of that cell
884      *
885      * @param cellX X coordinate of the cell
886      * @param cellY Y coordinate of the cell
887      *
888      * @param result Array of 2 ints to hold the x and y coordinate of the point
889      */
cellToPoint(int cellX, int cellY, int[] result)890     void cellToPoint(int cellX, int cellY, int[] result) {
891         final int hStartPadding = getPaddingLeft();
892         final int vStartPadding = getPaddingTop();
893 
894         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
895         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
896     }
897 
898     /**
899      * Given a cell coordinate, return the point that represents the upper left corner of that cell
900      *
901      * @param cellX X coordinate of the cell
902      * @param cellY Y coordinate of the cell
903      *
904      * @param result Array of 2 ints to hold the x and y coordinate of the point
905      */
cellToCenterPoint(int cellX, int cellY, int[] result)906     void cellToCenterPoint(int cellX, int cellY, int[] result) {
907         final int hStartPadding = getPaddingLeft();
908         final int vStartPadding = getPaddingTop();
909 
910         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) + mCellWidth / 2;
911         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) + mCellHeight / 2;
912     }
913 
getCellWidth()914     int getCellWidth() {
915         return mCellWidth;
916     }
917 
getCellHeight()918     int getCellHeight() {
919         return mCellHeight;
920     }
921 
getWidthGap()922     int getWidthGap() {
923         return mWidthGap;
924     }
925 
getHeightGap()926     int getHeightGap() {
927         return mHeightGap;
928     }
929 
getContentRect(Rect r)930     Rect getContentRect(Rect r) {
931         if (r == null) {
932             r = new Rect();
933         }
934         int left = getPaddingLeft();
935         int top = getPaddingTop();
936         int right = left + getWidth() - mPaddingLeft - mPaddingRight;
937         int bottom = top + getHeight() - mPaddingTop - mPaddingBottom;
938         r.set(left, top, right, bottom);
939         return r;
940     }
941 
942     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)943     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
944         // TODO: currently ignoring padding
945 
946         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
947         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
948 
949         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
950         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
951 
952         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
953             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
954         }
955 
956         int numWidthGaps = mCountX - 1;
957         int numHeightGaps = mCountY - 1;
958 
959         if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
960             int hSpace = widthSpecSize - mPaddingLeft - mPaddingRight;
961             int vSpace = heightSpecSize - mPaddingTop - mPaddingBottom;
962             int hFreeSpace = hSpace - (mCountX * mOriginalCellWidth);
963             int vFreeSpace = vSpace - (mCountY * mOriginalCellHeight);
964             mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
965             mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
966             mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
967         } else {
968             mWidthGap = mOriginalWidthGap;
969             mHeightGap = mOriginalHeightGap;
970         }
971 
972         // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
973         int newWidth = widthSpecSize;
974         int newHeight = heightSpecSize;
975         if (widthSpecMode == MeasureSpec.AT_MOST) {
976             newWidth = mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
977                 ((mCountX - 1) * mWidthGap);
978             newHeight = mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
979                 ((mCountY - 1) * mHeightGap);
980             setMeasuredDimension(newWidth, newHeight);
981         }
982 
983         int count = getChildCount();
984         for (int i = 0; i < count; i++) {
985             View child = getChildAt(i);
986             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - mPaddingLeft -
987                     mPaddingRight, MeasureSpec.EXACTLY);
988             int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - mPaddingTop -
989                     mPaddingBottom, MeasureSpec.EXACTLY);
990             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
991         }
992         setMeasuredDimension(newWidth, newHeight);
993     }
994 
995     @Override
onLayout(boolean changed, int l, int t, int r, int b)996     protected void onLayout(boolean changed, int l, int t, int r, int b) {
997         int count = getChildCount();
998         for (int i = 0; i < count; i++) {
999             View child = getChildAt(i);
1000             child.layout(mPaddingLeft, mPaddingTop,
1001                     r - l - mPaddingRight, b - t - mPaddingBottom);
1002         }
1003     }
1004 
1005     @Override
onSizeChanged(int w, int h, int oldw, int oldh)1006     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1007         super.onSizeChanged(w, h, oldw, oldh);
1008         mBackgroundRect.set(0, 0, w, h);
1009         mForegroundRect.set(mForegroundPadding, mForegroundPadding,
1010                 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
1011         updateGlowRect();
1012     }
1013 
1014     @Override
setChildrenDrawingCacheEnabled(boolean enabled)1015     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
1016         mChildren.setChildrenDrawingCacheEnabled(enabled);
1017     }
1018 
1019     @Override
setChildrenDrawnWithCacheEnabled(boolean enabled)1020     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
1021         mChildren.setChildrenDrawnWithCacheEnabled(enabled);
1022     }
1023 
getBackgroundAlpha()1024     public float getBackgroundAlpha() {
1025         return mBackgroundAlpha;
1026     }
1027 
setFastBackgroundAlpha(float alpha)1028     public void setFastBackgroundAlpha(float alpha) {
1029         mBackgroundAlpha = alpha;
1030     }
1031 
setBackgroundAlphaMultiplier(float multiplier)1032     public void setBackgroundAlphaMultiplier(float multiplier) {
1033         mBackgroundAlphaMultiplier = multiplier;
1034     }
1035 
getBackgroundAlphaMultiplier()1036     public float getBackgroundAlphaMultiplier() {
1037         return mBackgroundAlphaMultiplier;
1038     }
1039 
setBackgroundAlpha(float alpha)1040     public void setBackgroundAlpha(float alpha) {
1041         mBackgroundAlpha = alpha;
1042         invalidate();
1043     }
1044 
1045     // Need to return true to let the view system know we know how to handle alpha-- this is
1046     // because when our children have an alpha of 0.0f, they are still rendering their "dimmed"
1047     // versions
1048     @Override
onSetAlpha(int alpha)1049     protected boolean onSetAlpha(int alpha) {
1050         return true;
1051     }
1052 
setAlpha(float alpha)1053     public void setAlpha(float alpha) {
1054         setChildrenAlpha(alpha);
1055         super.setAlpha(alpha);
1056     }
1057 
setFastAlpha(float alpha)1058     public void setFastAlpha(float alpha) {
1059         setFastChildrenAlpha(alpha);
1060         super.setFastAlpha(alpha);
1061     }
1062 
setChildrenAlpha(float alpha)1063     private void setChildrenAlpha(float alpha) {
1064         final int childCount = getChildCount();
1065         for (int i = 0; i < childCount; i++) {
1066             getChildAt(i).setAlpha(alpha);
1067         }
1068     }
1069 
setFastChildrenAlpha(float alpha)1070     private void setFastChildrenAlpha(float alpha) {
1071         final int childCount = getChildCount();
1072         for (int i = 0; i < childCount; i++) {
1073             getChildAt(i).setFastAlpha(alpha);
1074         }
1075     }
1076 
getChildAt(int x, int y)1077     public View getChildAt(int x, int y) {
1078         return mChildren.getChildAt(x, y);
1079     }
1080 
animateChildToPosition(final View child, int cellX, int cellY, int duration, int delay)1081     public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
1082             int delay) {
1083         CellLayoutChildren clc = getChildrenLayout();
1084         if (clc.indexOfChild(child) != -1 && !mOccupied[cellX][cellY]) {
1085             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1086             final ItemInfo info = (ItemInfo) child.getTag();
1087 
1088             // We cancel any existing animations
1089             if (mReorderAnimators.containsKey(lp)) {
1090                 mReorderAnimators.get(lp).cancel();
1091                 mReorderAnimators.remove(lp);
1092             }
1093 
1094             int oldX = lp.x;
1095             int oldY = lp.y;
1096             mOccupied[lp.cellX][lp.cellY] = false;
1097             mOccupied[cellX][cellY] = true;
1098 
1099             lp.isLockedToGrid = true;
1100             lp.cellX = info.cellX = cellX;
1101             lp.cellY = info.cellY = cellY;
1102             clc.setupLp(lp);
1103             lp.isLockedToGrid = false;
1104             int newX = lp.x;
1105             int newY = lp.y;
1106 
1107             lp.x = oldX;
1108             lp.y = oldY;
1109             child.requestLayout();
1110 
1111             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", oldX, newX);
1112             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", oldY, newY);
1113             ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, x, y);
1114             oa.setDuration(duration);
1115             mReorderAnimators.put(lp, oa);
1116             oa.addUpdateListener(new AnimatorUpdateListener() {
1117                 public void onAnimationUpdate(ValueAnimator animation) {
1118                     child.requestLayout();
1119                 }
1120             });
1121             oa.addListener(new AnimatorListenerAdapter() {
1122                 boolean cancelled = false;
1123                 public void onAnimationEnd(Animator animation) {
1124                     // If the animation was cancelled, it means that another animation
1125                     // has interrupted this one, and we don't want to lock the item into
1126                     // place just yet.
1127                     if (!cancelled) {
1128                         lp.isLockedToGrid = true;
1129                     }
1130                     if (mReorderAnimators.containsKey(lp)) {
1131                         mReorderAnimators.remove(lp);
1132                     }
1133                 }
1134                 public void onAnimationCancel(Animator animation) {
1135                     cancelled = true;
1136                 }
1137             });
1138             oa.setStartDelay(delay);
1139             oa.start();
1140             return true;
1141         }
1142         return false;
1143     }
1144 
1145     /**
1146      * Estimate where the top left cell of the dragged item will land if it is dropped.
1147      *
1148      * @param originX The X value of the top left corner of the item
1149      * @param originY The Y value of the top left corner of the item
1150      * @param spanX The number of horizontal cells that the item spans
1151      * @param spanY The number of vertical cells that the item spans
1152      * @param result The estimated drop cell X and Y.
1153      */
estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result)1154     void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
1155         final int countX = mCountX;
1156         final int countY = mCountY;
1157 
1158         // pointToCellRounded takes the top left of a cell but will pad that with
1159         // cellWidth/2 and cellHeight/2 when finding the matching cell
1160         pointToCellRounded(originX, originY, result);
1161 
1162         // If the item isn't fully on this screen, snap to the edges
1163         int rightOverhang = result[0] + spanX - countX;
1164         if (rightOverhang > 0) {
1165             result[0] -= rightOverhang; // Snap to right
1166         }
1167         result[0] = Math.max(0, result[0]); // Snap to left
1168         int bottomOverhang = result[1] + spanY - countY;
1169         if (bottomOverhang > 0) {
1170             result[1] -= bottomOverhang; // Snap to bottom
1171         }
1172         result[1] = Math.max(0, result[1]); // Snap to top
1173     }
1174 
visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int spanX, int spanY, Point dragOffset, Rect dragRegion)1175     void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY,
1176             int spanX, int spanY, Point dragOffset, Rect dragRegion) {
1177 
1178         final int oldDragCellX = mDragCell[0];
1179         final int oldDragCellY = mDragCell[1];
1180         final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell);
1181         if (v != null && dragOffset == null) {
1182             mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1183         } else {
1184             mDragCenter.set(originX, originY);
1185         }
1186 
1187         if (dragOutline == null && v == null) {
1188             if (mCrosshairsDrawable != null) {
1189                 invalidate();
1190             }
1191             return;
1192         }
1193 
1194         if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) {
1195             // Find the top left corner of the rect the object will occupy
1196             final int[] topLeft = mTmpPoint;
1197             cellToPoint(nearest[0], nearest[1], topLeft);
1198 
1199             int left = topLeft[0];
1200             int top = topLeft[1];
1201 
1202             if (v != null && dragOffset == null) {
1203                 // When drawing the drag outline, it did not account for margin offsets
1204                 // added by the view's parent.
1205                 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1206                 left += lp.leftMargin;
1207                 top += lp.topMargin;
1208 
1209                 // Offsets due to the size difference between the View and the dragOutline.
1210                 // There is a size difference to account for the outer blur, which may lie
1211                 // outside the bounds of the view.
1212                 top += (v.getHeight() - dragOutline.getHeight()) / 2;
1213                 // We center about the x axis
1214                 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1215                         - dragOutline.getWidth()) / 2;
1216             } else {
1217                 if (dragOffset != null && dragRegion != null) {
1218                     // Center the drag region *horizontally* in the cell and apply a drag
1219                     // outline offset
1220                     left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1221                              - dragRegion.width()) / 2;
1222                     top += dragOffset.y;
1223                 } else {
1224                     // Center the drag outline in the cell
1225                     left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1226                             - dragOutline.getWidth()) / 2;
1227                     top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1228                             - dragOutline.getHeight()) / 2;
1229                 }
1230             }
1231 
1232             final int oldIndex = mDragOutlineCurrent;
1233             mDragOutlineAnims[oldIndex].animateOut();
1234             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
1235 
1236             mDragOutlines[mDragOutlineCurrent].set(left, top);
1237             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1238             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
1239         }
1240 
1241         // If we are drawing crosshairs, the entire CellLayout needs to be invalidated
1242         if (mCrosshairsDrawable != null) {
1243             invalidate();
1244         }
1245     }
1246 
clearDragOutlines()1247     public void clearDragOutlines() {
1248         final int oldIndex = mDragOutlineCurrent;
1249         mDragOutlineAnims[oldIndex].animateOut();
1250         mDragCell[0] = -1;
1251         mDragCell[1] = -1;
1252     }
1253 
1254     /**
1255      * Find a vacant area that will fit the given bounds nearest the requested
1256      * cell location. Uses Euclidean distance to score multiple vacant areas.
1257      *
1258      * @param pixelX The X location at which you want to search for a vacant area.
1259      * @param pixelY The Y location at which you want to search for a vacant area.
1260      * @param spanX Horizontal span of the object.
1261      * @param spanY Vertical span of the object.
1262      * @param result Array in which to place the result, or null (in which case a new array will
1263      *        be allocated)
1264      * @return The X, Y cell of a vacant area that can contain this object,
1265      *         nearest the requested location.
1266      */
findNearestVacantArea( int pixelX, int pixelY, int spanX, int spanY, int[] result)1267     int[] findNearestVacantArea(
1268             int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1269         return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
1270     }
1271 
1272     /**
1273      * Find a vacant area that will fit the given bounds nearest the requested
1274      * cell location. Uses Euclidean distance to score multiple vacant areas.
1275      *
1276      * @param pixelX The X location at which you want to search for a vacant area.
1277      * @param pixelY The Y location at which you want to search for a vacant area.
1278      * @param spanX Horizontal span of the object.
1279      * @param spanY Vertical span of the object.
1280      * @param ignoreOccupied If true, the result can be an occupied cell
1281      * @param result Array in which to place the result, or null (in which case a new array will
1282      *        be allocated)
1283      * @return The X, Y cell of a vacant area that can contain this object,
1284      *         nearest the requested location.
1285      */
findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, boolean ignoreOccupied, int[] result)1286     int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1287             boolean ignoreOccupied, int[] result) {
1288         // mark space take by ignoreView as available (method checks if ignoreView is null)
1289         markCellsAsUnoccupiedForView(ignoreView);
1290 
1291         // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1292         // to the center of the item, but we are searching based on the top-left cell, so
1293         // we translate the point over to correspond to the top-left.
1294         pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1295         pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1296 
1297         // Keep track of best-scoring drop area
1298         final int[] bestXY = result != null ? result : new int[2];
1299         double bestDistance = Double.MAX_VALUE;
1300 
1301         final int countX = mCountX;
1302         final int countY = mCountY;
1303         final boolean[][] occupied = mOccupied;
1304 
1305         for (int y = 0; y < countY - (spanY - 1); y++) {
1306             inner:
1307             for (int x = 0; x < countX - (spanX - 1); x++) {
1308                 if (ignoreOccupied) {
1309                     for (int i = 0; i < spanX; i++) {
1310                         for (int j = 0; j < spanY; j++) {
1311                             if (occupied[x + i][y + j]) {
1312                                 // small optimization: we can skip to after the column we
1313                                 // just found an occupied cell
1314                                 x += i;
1315                                 continue inner;
1316                             }
1317                         }
1318                     }
1319                 }
1320                 final int[] cellXY = mTmpXY;
1321                 cellToCenterPoint(x, y, cellXY);
1322 
1323                 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1324                         + Math.pow(cellXY[1] - pixelY, 2));
1325                 if (distance <= bestDistance) {
1326                     bestDistance = distance;
1327                     bestXY[0] = x;
1328                     bestXY[1] = y;
1329                 }
1330             }
1331         }
1332         // re-mark space taken by ignoreView as occupied
1333         markCellsAsOccupiedForView(ignoreView);
1334 
1335         // Return -1, -1 if no suitable location found
1336         if (bestDistance == Double.MAX_VALUE) {
1337             bestXY[0] = -1;
1338             bestXY[1] = -1;
1339         }
1340         return bestXY;
1341     }
1342 
1343     /**
1344      * Find a vacant area that will fit the given bounds nearest the requested
1345      * cell location. Uses Euclidean distance to score multiple vacant areas.
1346      *
1347      * @param pixelX The X location at which you want to search for a vacant area.
1348      * @param pixelY The Y location at which you want to search for a vacant area.
1349      * @param spanX Horizontal span of the object.
1350      * @param spanY Vertical span of the object.
1351      * @param ignoreView Considers space occupied by this view as unoccupied
1352      * @param result Previously returned value to possibly recycle.
1353      * @return The X, Y cell of a vacant area that can contain this object,
1354      *         nearest the requested location.
1355      */
findNearestVacantArea( int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result)1356     int[] findNearestVacantArea(
1357             int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
1358         return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
1359     }
1360 
1361     /**
1362      * Find a starting cell position that will fit the given bounds nearest the requested
1363      * cell location. Uses Euclidean distance to score multiple vacant areas.
1364      *
1365      * @param pixelX The X location at which you want to search for a vacant area.
1366      * @param pixelY The Y location at which you want to search for a vacant area.
1367      * @param spanX Horizontal span of the object.
1368      * @param spanY Vertical span of the object.
1369      * @param ignoreView Considers space occupied by this view as unoccupied
1370      * @param result Previously returned value to possibly recycle.
1371      * @return The X, Y cell of a vacant area that can contain this object,
1372      *         nearest the requested location.
1373      */
findNearestArea( int pixelX, int pixelY, int spanX, int spanY, int[] result)1374     int[] findNearestArea(
1375             int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1376         return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
1377     }
1378 
existsEmptyCell()1379     boolean existsEmptyCell() {
1380         return findCellForSpan(null, 1, 1);
1381     }
1382 
1383     /**
1384      * Finds the upper-left coordinate of the first rectangle in the grid that can
1385      * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
1386      * then this method will only return coordinates for rectangles that contain the cell
1387      * (intersectX, intersectY)
1388      *
1389      * @param cellXY The array that will contain the position of a vacant cell if such a cell
1390      *               can be found.
1391      * @param spanX The horizontal span of the cell we want to find.
1392      * @param spanY The vertical span of the cell we want to find.
1393      *
1394      * @return True if a vacant cell of the specified dimension was found, false otherwise.
1395      */
findCellForSpan(int[] cellXY, int spanX, int spanY)1396     boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
1397         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null);
1398     }
1399 
1400     /**
1401      * Like above, but ignores any cells occupied by the item "ignoreView"
1402      *
1403      * @param cellXY The array that will contain the position of a vacant cell if such a cell
1404      *               can be found.
1405      * @param spanX The horizontal span of the cell we want to find.
1406      * @param spanY The vertical span of the cell we want to find.
1407      * @param ignoreView The home screen item we should treat as not occupying any space
1408      * @return
1409      */
findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView)1410     boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
1411         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView);
1412     }
1413 
1414     /**
1415      * Like above, but if intersectX and intersectY are not -1, then this method will try to
1416      * return coordinates for rectangles that contain the cell [intersectX, intersectY]
1417      *
1418      * @param spanX The horizontal span of the cell we want to find.
1419      * @param spanY The vertical span of the cell we want to find.
1420      * @param ignoreView The home screen item we should treat as not occupying any space
1421      * @param intersectX The X coordinate of the cell that we should try to overlap
1422      * @param intersectX The Y coordinate of the cell that we should try to overlap
1423      *
1424      * @return True if a vacant cell of the specified dimension was found, false otherwise.
1425      */
findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, int intersectX, int intersectY)1426     boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
1427             int intersectX, int intersectY) {
1428         return findCellForSpanThatIntersectsIgnoring(
1429                 cellXY, spanX, spanY, intersectX, intersectY, null);
1430     }
1431 
1432     /**
1433      * The superset of the above two methods
1434      */
findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, int intersectX, int intersectY, View ignoreView)1435     boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
1436             int intersectX, int intersectY, View ignoreView) {
1437         // mark space take by ignoreView as available (method checks if ignoreView is null)
1438         markCellsAsUnoccupiedForView(ignoreView);
1439 
1440         boolean foundCell = false;
1441         while (true) {
1442             int startX = 0;
1443             if (intersectX >= 0) {
1444                 startX = Math.max(startX, intersectX - (spanX - 1));
1445             }
1446             int endX = mCountX - (spanX - 1);
1447             if (intersectX >= 0) {
1448                 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
1449             }
1450             int startY = 0;
1451             if (intersectY >= 0) {
1452                 startY = Math.max(startY, intersectY - (spanY - 1));
1453             }
1454             int endY = mCountY - (spanY - 1);
1455             if (intersectY >= 0) {
1456                 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
1457             }
1458 
1459             for (int y = startY; y < endY && !foundCell; y++) {
1460                 inner:
1461                 for (int x = startX; x < endX; x++) {
1462                     for (int i = 0; i < spanX; i++) {
1463                         for (int j = 0; j < spanY; j++) {
1464                             if (mOccupied[x + i][y + j]) {
1465                                 // small optimization: we can skip to after the column we just found
1466                                 // an occupied cell
1467                                 x += i;
1468                                 continue inner;
1469                             }
1470                         }
1471                     }
1472                     if (cellXY != null) {
1473                         cellXY[0] = x;
1474                         cellXY[1] = y;
1475                     }
1476                     foundCell = true;
1477                     break;
1478                 }
1479             }
1480             if (intersectX == -1 && intersectY == -1) {
1481                 break;
1482             } else {
1483                 // if we failed to find anything, try again but without any requirements of
1484                 // intersecting
1485                 intersectX = -1;
1486                 intersectY = -1;
1487                 continue;
1488             }
1489         }
1490 
1491         // re-mark space taken by ignoreView as occupied
1492         markCellsAsOccupiedForView(ignoreView);
1493         return foundCell;
1494     }
1495 
1496     /**
1497      * A drag event has begun over this layout.
1498      * It may have begun over this layout (in which case onDragChild is called first),
1499      * or it may have begun on another layout.
1500      */
onDragEnter()1501     void onDragEnter() {
1502         if (!mDragging) {
1503             // Fade in the drag indicators
1504             if (mCrosshairsAnimator != null) {
1505                 mCrosshairsAnimator.animateIn();
1506             }
1507         }
1508         mDragging = true;
1509     }
1510 
1511     /**
1512      * Called when drag has left this CellLayout or has been completed (successfully or not)
1513      */
onDragExit()1514     void onDragExit() {
1515         // This can actually be called when we aren't in a drag, e.g. when adding a new
1516         // item to this layout via the customize drawer.
1517         // Guard against that case.
1518         if (mDragging) {
1519             mDragging = false;
1520 
1521             // Fade out the drag indicators
1522             if (mCrosshairsAnimator != null) {
1523                 mCrosshairsAnimator.animateOut();
1524             }
1525         }
1526 
1527         // Invalidate the drag data
1528         mDragCell[0] = -1;
1529         mDragCell[1] = -1;
1530         mDragOutlineAnims[mDragOutlineCurrent].animateOut();
1531         mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
1532 
1533         setIsDragOverlapping(false);
1534     }
1535 
1536     /**
1537      * Mark a child as having been dropped.
1538      * At the beginning of the drag operation, the child may have been on another
1539      * screen, but it is re-parented before this method is called.
1540      *
1541      * @param child The child that is being dropped
1542      */
onDropChild(View child)1543     void onDropChild(View child) {
1544         if (child != null) {
1545             LayoutParams lp = (LayoutParams) child.getLayoutParams();
1546             lp.dropped = true;
1547             child.requestLayout();
1548         }
1549     }
1550 
1551     /**
1552      * Computes a bounding rectangle for a range of cells
1553      *
1554      * @param cellX X coordinate of upper left corner expressed as a cell position
1555      * @param cellY Y coordinate of upper left corner expressed as a cell position
1556      * @param cellHSpan Width in cells
1557      * @param cellVSpan Height in cells
1558      * @param resultRect Rect into which to put the results
1559      */
cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect)1560     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
1561         final int cellWidth = mCellWidth;
1562         final int cellHeight = mCellHeight;
1563         final int widthGap = mWidthGap;
1564         final int heightGap = mHeightGap;
1565 
1566         final int hStartPadding = getPaddingLeft();
1567         final int vStartPadding = getPaddingTop();
1568 
1569         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
1570         int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
1571 
1572         int x = hStartPadding + cellX * (cellWidth + widthGap);
1573         int y = vStartPadding + cellY * (cellHeight + heightGap);
1574 
1575         resultRect.set(x, y, x + width, y + height);
1576     }
1577 
1578     /**
1579      * Computes the required horizontal and vertical cell spans to always
1580      * fit the given rectangle.
1581      *
1582      * @param width Width in pixels
1583      * @param height Height in pixels
1584      * @param result An array of length 2 in which to store the result (may be null).
1585      */
rectToCell(int width, int height, int[] result)1586     public int[] rectToCell(int width, int height, int[] result) {
1587         return rectToCell(getResources(), width, height, result);
1588     }
1589 
rectToCell(Resources resources, int width, int height, int[] result)1590     public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
1591         // Always assume we're working with the smallest span to make sure we
1592         // reserve enough space in both orientations.
1593         int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
1594         int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
1595         int smallerSize = Math.min(actualWidth, actualHeight);
1596 
1597         // Always round up to next largest cell
1598         int spanX = (int) Math.ceil(width / (float) smallerSize);
1599         int spanY = (int) Math.ceil(height / (float) smallerSize);
1600 
1601         if (result == null) {
1602             return new int[] { spanX, spanY };
1603         }
1604         result[0] = spanX;
1605         result[1] = spanY;
1606         return result;
1607     }
1608 
cellSpansToSize(int hSpans, int vSpans)1609     public int[] cellSpansToSize(int hSpans, int vSpans) {
1610         int[] size = new int[2];
1611         size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
1612         size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
1613         return size;
1614     }
1615 
1616     /**
1617      * Calculate the grid spans needed to fit given item
1618      */
calculateSpans(ItemInfo info)1619     public void calculateSpans(ItemInfo info) {
1620         final int minWidth;
1621         final int minHeight;
1622 
1623         if (info instanceof LauncherAppWidgetInfo) {
1624             minWidth = ((LauncherAppWidgetInfo) info).minWidth;
1625             minHeight = ((LauncherAppWidgetInfo) info).minHeight;
1626         } else if (info instanceof PendingAddWidgetInfo) {
1627             minWidth = ((PendingAddWidgetInfo) info).minWidth;
1628             minHeight = ((PendingAddWidgetInfo) info).minHeight;
1629         } else {
1630             // It's not a widget, so it must be 1x1
1631             info.spanX = info.spanY = 1;
1632             return;
1633         }
1634         int[] spans = rectToCell(minWidth, minHeight, null);
1635         info.spanX = spans[0];
1636         info.spanY = spans[1];
1637     }
1638 
1639     /**
1640      * Find the first vacant cell, if there is one.
1641      *
1642      * @param vacant Holds the x and y coordinate of the vacant cell
1643      * @param spanX Horizontal cell span.
1644      * @param spanY Vertical cell span.
1645      *
1646      * @return True if a vacant cell was found
1647      */
getVacantCell(int[] vacant, int spanX, int spanY)1648     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
1649 
1650         return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
1651     }
1652 
findVacantCell(int[] vacant, int spanX, int spanY, int xCount, int yCount, boolean[][] occupied)1653     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
1654             int xCount, int yCount, boolean[][] occupied) {
1655 
1656         for (int y = 0; y < yCount; y++) {
1657             for (int x = 0; x < xCount; x++) {
1658                 boolean available = !occupied[x][y];
1659 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
1660                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
1661                         available = available && !occupied[i][j];
1662                         if (!available) break out;
1663                     }
1664                 }
1665 
1666                 if (available) {
1667                     vacant[0] = x;
1668                     vacant[1] = y;
1669                     return true;
1670                 }
1671             }
1672         }
1673 
1674         return false;
1675     }
1676 
clearOccupiedCells()1677     private void clearOccupiedCells() {
1678         for (int x = 0; x < mCountX; x++) {
1679             for (int y = 0; y < mCountY; y++) {
1680                 mOccupied[x][y] = false;
1681             }
1682         }
1683     }
1684 
1685     /**
1686      * Given a view, determines how much that view can be expanded in all directions, in terms of
1687      * whether or not there are other items occupying adjacent cells. Used by the
1688      * AppWidgetResizeFrame to determine how the widget can be resized.
1689      */
getExpandabilityArrayForView(View view, int[] expandability)1690     public void getExpandabilityArrayForView(View view, int[] expandability) {
1691         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
1692         boolean flag;
1693 
1694         expandability[AppWidgetResizeFrame.LEFT] = 0;
1695         for (int x = lp.cellX - 1; x >= 0; x--) {
1696             flag = false;
1697             for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
1698                 if (mOccupied[x][y]) flag = true;
1699             }
1700             if (flag) break;
1701             expandability[AppWidgetResizeFrame.LEFT]++;
1702         }
1703 
1704         expandability[AppWidgetResizeFrame.TOP] = 0;
1705         for (int y = lp.cellY - 1; y >= 0; y--) {
1706             flag = false;
1707             for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
1708                 if (mOccupied[x][y]) flag = true;
1709             }
1710             if (flag) break;
1711             expandability[AppWidgetResizeFrame.TOP]++;
1712         }
1713 
1714         expandability[AppWidgetResizeFrame.RIGHT] = 0;
1715         for (int x = lp.cellX + lp.cellHSpan; x < mCountX; x++) {
1716             flag = false;
1717             for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
1718                 if (mOccupied[x][y]) flag = true;
1719             }
1720             if (flag) break;
1721             expandability[AppWidgetResizeFrame.RIGHT]++;
1722         }
1723 
1724         expandability[AppWidgetResizeFrame.BOTTOM] = 0;
1725         for (int y = lp.cellY + lp.cellVSpan; y < mCountY; y++) {
1726             flag = false;
1727             for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
1728                 if (mOccupied[x][y]) flag = true;
1729             }
1730             if (flag) break;
1731             expandability[AppWidgetResizeFrame.BOTTOM]++;
1732         }
1733     }
1734 
onMove(View view, int newCellX, int newCellY)1735     public void onMove(View view, int newCellX, int newCellY) {
1736         LayoutParams lp = (LayoutParams) view.getLayoutParams();
1737         markCellsAsUnoccupiedForView(view);
1738         markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true);
1739     }
1740 
markCellsAsOccupiedForView(View view)1741     public void markCellsAsOccupiedForView(View view) {
1742         if (view == null || view.getParent() != mChildren) return;
1743         LayoutParams lp = (LayoutParams) view.getLayoutParams();
1744         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
1745     }
1746 
markCellsAsUnoccupiedForView(View view)1747     public void markCellsAsUnoccupiedForView(View view) {
1748         if (view == null || view.getParent() != mChildren) return;
1749         LayoutParams lp = (LayoutParams) view.getLayoutParams();
1750         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
1751     }
1752 
markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value)1753     private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) {
1754         for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
1755             for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
1756                 mOccupied[x][y] = value;
1757             }
1758         }
1759     }
1760 
getDesiredWidth()1761     public int getDesiredWidth() {
1762         return mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
1763                 (Math.max((mCountX - 1), 0) * mWidthGap);
1764     }
1765 
getDesiredHeight()1766     public int getDesiredHeight()  {
1767         return mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
1768                 (Math.max((mCountY - 1), 0) * mHeightGap);
1769     }
1770 
isOccupied(int x, int y)1771     public boolean isOccupied(int x, int y) {
1772         if (x < mCountX && y < mCountY) {
1773             return mOccupied[x][y];
1774         } else {
1775             throw new RuntimeException("Position exceeds the bound of this CellLayout");
1776         }
1777     }
1778 
1779     @Override
generateLayoutParams(AttributeSet attrs)1780     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1781         return new CellLayout.LayoutParams(getContext(), attrs);
1782     }
1783 
1784     @Override
checkLayoutParams(ViewGroup.LayoutParams p)1785     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1786         return p instanceof CellLayout.LayoutParams;
1787     }
1788 
1789     @Override
generateLayoutParams(ViewGroup.LayoutParams p)1790     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1791         return new CellLayout.LayoutParams(p);
1792     }
1793 
1794     public static class CellLayoutAnimationController extends LayoutAnimationController {
CellLayoutAnimationController(Animation animation, float delay)1795         public CellLayoutAnimationController(Animation animation, float delay) {
1796             super(animation, delay);
1797         }
1798 
1799         @Override
getDelayForView(View view)1800         protected long getDelayForView(View view) {
1801             return (int) (Math.random() * 150);
1802         }
1803     }
1804 
1805     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1806         /**
1807          * Horizontal location of the item in the grid.
1808          */
1809         @ViewDebug.ExportedProperty
1810         public int cellX;
1811 
1812         /**
1813          * Vertical location of the item in the grid.
1814          */
1815         @ViewDebug.ExportedProperty
1816         public int cellY;
1817 
1818         /**
1819          * Number of cells spanned horizontally by the item.
1820          */
1821         @ViewDebug.ExportedProperty
1822         public int cellHSpan;
1823 
1824         /**
1825          * Number of cells spanned vertically by the item.
1826          */
1827         @ViewDebug.ExportedProperty
1828         public int cellVSpan;
1829 
1830         /**
1831          * Indicates whether the item will set its x, y, width and height parameters freely,
1832          * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
1833          */
1834         public boolean isLockedToGrid = true;
1835 
1836         // X coordinate of the view in the layout.
1837         @ViewDebug.ExportedProperty
1838         int x;
1839         // Y coordinate of the view in the layout.
1840         @ViewDebug.ExportedProperty
1841         int y;
1842 
1843         boolean dropped;
1844 
LayoutParams(Context c, AttributeSet attrs)1845         public LayoutParams(Context c, AttributeSet attrs) {
1846             super(c, attrs);
1847             cellHSpan = 1;
1848             cellVSpan = 1;
1849         }
1850 
LayoutParams(ViewGroup.LayoutParams source)1851         public LayoutParams(ViewGroup.LayoutParams source) {
1852             super(source);
1853             cellHSpan = 1;
1854             cellVSpan = 1;
1855         }
1856 
LayoutParams(LayoutParams source)1857         public LayoutParams(LayoutParams source) {
1858             super(source);
1859             this.cellX = source.cellX;
1860             this.cellY = source.cellY;
1861             this.cellHSpan = source.cellHSpan;
1862             this.cellVSpan = source.cellVSpan;
1863         }
1864 
LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan)1865         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
1866             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
1867             this.cellX = cellX;
1868             this.cellY = cellY;
1869             this.cellHSpan = cellHSpan;
1870             this.cellVSpan = cellVSpan;
1871         }
1872 
setup(int cellWidth, int cellHeight, int widthGap, int heightGap)1873         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
1874             if (isLockedToGrid) {
1875                 final int myCellHSpan = cellHSpan;
1876                 final int myCellVSpan = cellVSpan;
1877                 final int myCellX = cellX;
1878                 final int myCellY = cellY;
1879 
1880                 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1881                         leftMargin - rightMargin;
1882                 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1883                         topMargin - bottomMargin;
1884                 x = myCellX * (cellWidth + widthGap) + leftMargin;
1885                 y = myCellY * (cellHeight + heightGap) + topMargin;
1886             }
1887         }
1888 
toString()1889         public String toString() {
1890             return "(" + this.cellX + ", " + this.cellY + ")";
1891         }
1892 
setWidth(int width)1893         public void setWidth(int width) {
1894             this.width = width;
1895         }
1896 
getWidth()1897         public int getWidth() {
1898             return width;
1899         }
1900 
setHeight(int height)1901         public void setHeight(int height) {
1902             this.height = height;
1903         }
1904 
getHeight()1905         public int getHeight() {
1906             return height;
1907         }
1908 
setX(int x)1909         public void setX(int x) {
1910             this.x = x;
1911         }
1912 
getX()1913         public int getX() {
1914             return x;
1915         }
1916 
setY(int y)1917         public void setY(int y) {
1918             this.y = y;
1919         }
1920 
getY()1921         public int getY() {
1922             return y;
1923         }
1924     }
1925 
1926     // This class stores info for two purposes:
1927     // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
1928     //    its spanX, spanY, and the screen it is on
1929     // 2. When long clicking on an empty cell in a CellLayout, we save information about the
1930     //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
1931     //    the CellLayout that was long clicked
1932     static final class CellInfo {
1933         View cell;
1934         int cellX = -1;
1935         int cellY = -1;
1936         int spanX;
1937         int spanY;
1938         int screen;
1939         long container;
1940 
1941         @Override
toString()1942         public String toString() {
1943             return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1944                     + ", x=" + cellX + ", y=" + cellY + "]";
1945         }
1946     }
1947 
lastDownOnOccupiedCell()1948     public boolean lastDownOnOccupiedCell() {
1949         return mLastDownOnOccupiedCell;
1950     }
1951 }
1952