blob: c57090d7cce1ab2f0259851a5319c892b1b44358 [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070020import android.animation.AnimatorListenerAdapter;
Brandon Keely50e6e562012-05-08 16:28:49 -070021import android.animation.AnimatorSet;
Chet Haase00397b12010-10-07 11:13:10 -070022import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070023import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohenc9735cf2015-01-23 16:11:55 -080025import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070029import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070030import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080031import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070032import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070033import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080034import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080035import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070036import android.graphics.drawable.Drawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080037import android.os.Build;
38import android.os.Bundle;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080040import android.support.v4.view.ViewCompat;
41import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
42import android.support.v4.widget.ExploreByTouchHelper;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070044import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070045import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080046import android.view.MotionEvent;
47import android.view.View;
Adam Cohenc9735cf2015-01-23 16:11:55 -080048import android.view.View.OnClickListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049import android.view.ViewDebug;
50import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080051import android.view.accessibility.AccessibilityEvent;
Winson Chungaafa03c2010-06-11 17:34:16 -070052import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070053import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070054import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080055
Daniel Sandler325dc232013-06-05 22:57:57 -040056import com.android.launcher3.FolderIcon.FolderRingAnimator;
Adam Cohenc9735cf2015-01-23 16:11:55 -080057import com.android.launcher3.LauncherAccessibilityDelegate.DragType;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070058
Adam Cohen69ce2e52011-07-03 19:25:21 -070059import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070060import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080061import java.util.Collections;
62import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070063import java.util.HashMap;
Adam Cohenc9735cf2015-01-23 16:11:55 -080064import java.util.List;
Adam Cohend41fbf52012-02-16 23:53:59 -080065import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070066
Michael Jurkabdb5c532011-02-01 15:05:06 -080067public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070068 static final String TAG = "CellLayout";
69
Adam Cohen2acce882012-03-28 19:03:19 -070070 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080071 private int mCellWidth;
72 private int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070073 private int mFixedCellWidth;
74 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070075
Adam Cohend22015c2010-07-26 22:02:18 -070076 private int mCountX;
77 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080078
Adam Cohen234c4cd2011-07-17 21:03:04 -070079 private int mOriginalWidthGap;
80 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080081 private int mWidthGap;
82 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070083 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070084 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070085 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086
Patrick Dubroyde7658b2010-09-27 11:15:43 -070087 // These are temporary variables to prevent having to allocate a new object just to
88 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070089 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070090 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070091 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070092
The Android Open Source Project31dd5032009-03-03 19:32:27 -080093 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080094 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070095 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080096
Michael Jurkadee05892010-07-27 10:01:56 -070097 private OnTouchListener mInterceptTouchListener;
98
Adam Cohen69ce2e52011-07-03 19:25:21 -070099 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700100 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700101
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700102 private float FOREGROUND_ALPHA_DAMPER = 0.65f;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700103 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700104 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700105 private float mBackgroundAlphaMultiplier = 1.0f;
Winson Chung59a488a2013-12-10 12:32:14 -0800106 private boolean mDrawBackground = true;
Adam Cohenf34bab52010-09-30 14:11:56 -0700107
Michael Jurka33945b22010-12-21 18:19:38 -0800108 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800109 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700110 private Drawable mOverScrollForegroundDrawable;
111 private Drawable mOverScrollLeft;
112 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700113 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700114 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700115 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700116
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700117 // These values allow a fixed measurement to be set on the CellLayout.
118 private int mFixedWidth = -1;
119 private int mFixedHeight = -1;
120
Michael Jurka33945b22010-12-21 18:19:38 -0800121 // If we're actively dragging something over this screen, mIsDragOverlapping is true
122 private boolean mIsDragOverlapping = false;
Adam Cohendedbd962013-07-11 14:21:49 -0700123 boolean mUseActiveGlowBackground = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700124
Winson Chung150fbab2010-09-29 17:14:26 -0700125 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700126 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800127 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700128 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700129 private InterruptibleInOutAnimator[] mDragOutlineAnims =
130 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700131
132 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700133 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700134 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700135
Sunny Goyal508da152014-08-14 10:53:27 -0700136 private final FastBitmapView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800137
Adam Cohen482ed822012-03-02 14:15:13 -0800138 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
139 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800140 private HashMap<View, ReorderPreviewAnimation>
141 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700142
143 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700144
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700145 // When a drag operation is in progress, holds the nearest cell to the touch point
146 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800147
Joe Onorato4be866d2010-10-10 11:26:02 -0700148 private boolean mDragging = false;
149
Patrick Dubroyce34a972010-10-19 10:34:32 -0700150 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700151 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700152
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800153 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700154 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800155
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800156 public static final int MODE_SHOW_REORDER_HINT = 0;
157 public static final int MODE_DRAG_OVER = 1;
158 public static final int MODE_ON_DROP = 2;
159 public static final int MODE_ON_DROP_EXTERNAL = 3;
160 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700161 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800162 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
163
Adam Cohena897f392012-04-27 18:12:05 -0700164 static final int LANDSCAPE = 0;
165 static final int PORTRAIT = 1;
166
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800167 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700168 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800169 private float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700170
Adam Cohen482ed822012-03-02 14:15:13 -0800171 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
172 private Rect mOccupiedRect = new Rect();
173 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700174 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700175 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700176 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800177
Winson Chung3a6e7f32013-10-09 15:50:52 -0700178 private Rect mTempRect = new Rect();
179
Michael Jurkaca993832012-06-29 15:17:04 -0700180 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700181
Adam Cohenc9735cf2015-01-23 16:11:55 -0800182 // Related to accessible drag and drop
183 DragAndDropAccessibilityDelegate mTouchHelper = new DragAndDropAccessibilityDelegate(this);
184 private boolean mUseTouchHelper = false;
185 OnClickListener mOldClickListener = null;
186 OnClickListener mOldWorkspaceListener = null;
187 private int mDownX = 0;
188 private int mDownY = 0;
189
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800190 public CellLayout(Context context) {
191 this(context, null);
192 }
193
194 public CellLayout(Context context, AttributeSet attrs) {
195 this(context, attrs, 0);
196 }
197
198 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
199 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700200 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700201
202 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
203 // the user where a dragged item will land when dropped.
204 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800205 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700206 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700207
Winson Chung892c74d2013-08-22 16:15:50 -0700208 LauncherAppState app = LauncherAppState.getInstance();
209 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800210 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
211
Winson Chung11a1a532013-09-13 11:14:45 -0700212 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800213 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700214 mWidthGap = mOriginalWidthGap = 0;
215 mHeightGap = mOriginalHeightGap = 0;
216 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700217 mCountX = (int) grid.numColumns;
218 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700219 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800220 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700221 mPreviousReorderDirection[0] = INVALID_DIRECTION;
222 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800223
224 a.recycle();
225
226 setAlwaysDrawnWithCacheEnabled(false);
227
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700228 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700229 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700230
Adam Cohen410f3cd2013-09-22 12:09:32 -0700231 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
232 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800233
Adam Cohenb5ba0972011-09-07 18:02:31 -0700234 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
235 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
236 mForegroundPadding =
237 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800238
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800239 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700240 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700241
Winson Chungb26f3d62011-06-02 10:49:29 -0700242 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700243 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700244
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700245 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700246 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700247 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700248 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800249 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700250 }
251
252 // When dragging things around the home screens, we show a green outline of
253 // where the item will land. The outlines gradually fade out, leaving a trail
254 // behind the drag path.
255 // Set up all the animations that are used to implement this fading.
256 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700257 final float fromAlphaValue = 0;
258 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700259
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700260 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700261
262 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700263 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100264 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700265 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700266 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700267 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700268 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 final Bitmap outline = (Bitmap)anim.getTag();
270
271 // If an animation is started and then stopped very quickly, we can still
272 // get spurious updates we've cleared the tag. Guard against this.
273 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700274 @SuppressWarnings("all") // suppress dead code warning
275 final boolean debug = false;
276 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700277 Object val = animation.getAnimatedValue();
278 Log.d(TAG, "anim " + thisIndex + " update: " + val +
279 ", isStopped " + anim.isStopped());
280 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 // Try to prevent it from continuing to run
282 animation.cancel();
283 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700284 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800285 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700286 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700287 }
288 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700289 // The animation holds a reference to the drag outline bitmap as long is it's
290 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700291 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700292 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700293 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700294 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700295 anim.setTag(null);
296 }
297 }
298 });
299 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700300 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700301
Michael Jurka18014792010-10-14 09:01:34 -0700302 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700303 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800304
Michael Jurkaa52570f2012-03-20 03:18:20 -0700305 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700306 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700307 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700308
Sunny Goyal508da152014-08-14 10:53:27 -0700309 mTouchFeedbackView = new FastBitmapView(context);
310 // Make the feedback view large enough to hold the blur bitmap.
311 addView(mTouchFeedbackView, (int) (grid.cellWidthPx * 1.5), (int) (grid.cellHeightPx * 1.5));
Michael Jurkaa52570f2012-03-20 03:18:20 -0700312 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700313 }
314
Adam Cohenc9735cf2015-01-23 16:11:55 -0800315 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
316 public void enableAccessibleDrag(boolean enable) {
317 mUseTouchHelper = enable;
318 if (!enable) {
319 ViewCompat.setAccessibilityDelegate(this, null);
320 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
321 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
322 setOnClickListener(mLauncher);
323 } else {
324 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
325 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
326 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
327 setOnClickListener(mTouchHelper);
328 }
329
330 // Invalidate the accessibility hierarchy
331 if (getParent() != null) {
332 getParent().notifySubtreeAccessibilityStateChanged(
333 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
334 }
335 }
336
337 @Override
338 public boolean dispatchHoverEvent(MotionEvent event) {
339 // Always attempt to dispatch hover events to accessibility first.
340 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
341 return true;
342 }
343 return super.dispatchHoverEvent(event);
344 }
345
346 @Override
347 public boolean dispatchTouchEvent(MotionEvent event) {
348 if (event.getAction() == MotionEvent.ACTION_DOWN) {
349 mDownX = (int) event.getX();
350 mDownY = (int) event.getY();
351 }
352 return super.dispatchTouchEvent(event);
353 }
354
355 @Override
356 public boolean onInterceptTouchEvent(MotionEvent ev) {
357 if (mUseTouchHelper ||
358 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
359 return true;
360 }
361 return false;
362 }
363
364 class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper implements OnClickListener {
365 private final Rect mTempRect = new Rect();
366
367 public DragAndDropAccessibilityDelegate(View forView) {
368 super(forView);
369 }
370
371 private int getViewIdAt(float x, float y) {
372 if (x < 0 || y < 0 || x > getMeasuredWidth() || y > getMeasuredHeight()) {
373 return ExploreByTouchHelper.INVALID_ID;
374 }
375
376 // Map coords to cell
377 int cellX = (int) Math.floor(x / (mCellWidth + mWidthGap));
378 int cellY = (int) Math.floor(y / (mCellHeight + mHeightGap));
379
380 // Map cell to id
381 int id = cellX * mCountY + cellY;
382 return id;
383 }
384
385 @Override
386 protected int getVirtualViewAt(float x, float y) {
387 return nearestDropLocation(getViewIdAt(x, y));
388 }
389
390 protected int nearestDropLocation(int id) {
391 int count = mCountX * mCountY;
392 for (int delta = 0; delta < count; delta++) {
393 if (id + delta <= (count - 1)) {
394 int target = intersectsValidDropTarget(id + delta);
395 if (target >= 0) {
396 return target;
397 }
398 } else if (id - delta >= 0) {
399 int target = intersectsValidDropTarget(id - delta);
400 if (target >= 0) {
401 return target;
402 }
403 }
404 }
405 return ExploreByTouchHelper.INVALID_ID;
406 }
407
408 /**
409 * Find the virtual view id corresponding to the top left corner of any drop region by which
410 * the passed id is contained. For an icon, this is simply
411 *
412 * @param id the id we're interested examining (ie. does it fit there?)
413 * @return the view id of the top left corner of a valid drop region or -1 if there is no
414 * such valid region. For the icon, this can just be -1 or id.
415 */
416 protected int intersectsValidDropTarget(int id) {
417 LauncherAccessibilityDelegate delegate =
418 LauncherAppState.getInstance().getAccessibilityDelegate();
419 LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
420
421 int y = id % mCountY;
422 int x = id / mCountY;
423
424 if (dragInfo.dragType == DragType.WIDGET) {
425 // For a widget, every cell must be vacant. In addition, we will return any valid
426 // drop target by which the passed id is contained.
427 boolean fits = false;
428
429 // These represent the amount that we can back off if we hit a problem. They
430 // get consumed as we move up and to the right, trying new regions.
431 int spanX = dragInfo.info.spanX;
432 int spanY = dragInfo.info.spanY;
433
434 for (int m = 0; m < spanX; m++) {
435 for (int n = 0; n < spanY; n++) {
436
437 fits = true;
438 int x0 = x - m;
439 int y0 = y - n;
440
441 if (x0 < 0 || y0 < 0) continue;
442
443 for (int i = x0; i < x0 + spanX; i++) {
444 if (!fits) break;
445 for (int j = y0; j < y0 + spanY; j++) {
446 if (i >= mCountX || j >= mCountY || mOccupied[i][j]) {
447 fits = false;
448 break;
449 }
450 }
451 }
452 if (fits) {
453 return x0 * mCountY + y0;
454 }
455 }
456 }
457 return -1;
458 } else {
459 // For an icon, we simply check the view directly below
460 View child = getChildAt(x, y);
461 if (child == null || child == dragInfo.item) {
462 // Empty cell. Good for an icon or folder.
463 return id;
464 } else if (dragInfo.dragType != DragType.FOLDER) {
465 // For icons, we can consider cells that have another icon or a folder.
466 ItemInfo info = (ItemInfo) child.getTag();
467 if (info instanceof AppInfo || info instanceof FolderInfo ||
468 info instanceof ShortcutInfo) {
469 return id;
470 }
471 }
472 return -1;
473 }
474 }
475
476 @Override
477 protected void getVisibleVirtualViews(List<Integer> virtualViews) {
478 // We create a virtual view for each cell of the grid
479 // The cell ids correspond to cells in reading order.
480 int nCells = mCountX * mCountY;
481
482 for (int i = 0; i < nCells; i++) {
483 if (intersectsValidDropTarget(i) >= 0) {
484 virtualViews.add(i);
485 }
486 }
487 }
488
489 @Override
490 protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
491 if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
492 String confirmation = getConfirmationForIconDrop(viewId);
493 LauncherAppState.getInstance().getAccessibilityDelegate()
494 .handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
495 return true;
496 }
497 return false;
498 }
499
500 @Override
501 public void onClick(View arg0) {
502 int viewId = getViewIdAt(mDownX, mDownY);
503
504 String confirmation = getConfirmationForIconDrop(viewId);
505 LauncherAppState.getInstance().getAccessibilityDelegate()
506 .handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
507 }
508
509 @Override
510 protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) {
511 if (id == ExploreByTouchHelper.INVALID_ID) {
512 throw new IllegalArgumentException("Invalid virtual view id");
513 }
514 // We're required to set something here.
515 event.setContentDescription("");
516 }
517
518 @Override
519 protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
520 if (id == ExploreByTouchHelper.INVALID_ID) {
521 throw new IllegalArgumentException("Invalid virtual view id");
522 }
523
524 node.setContentDescription(getLocationDescriptionForIconDrop(id));
525 node.setBoundsInParent(getItemBounds(id));
526
527 node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
528 node.setClickable(true);
529 node.setFocusable(true);
530 }
531
532 private String getLocationDescriptionForIconDrop(int id) {
533 LauncherAccessibilityDelegate delegate =
534 LauncherAppState.getInstance().getAccessibilityDelegate();
535 LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
536
537 int y = id % mCountY;
538 int x = id / mCountY;
539
540 Resources res = getContext().getResources();
541 View child = getChildAt(x, y);
542 if (child == null || child == dragInfo.item) {
543 return res.getString(R.string.move_to_empty_cell, x, y);
544 } else {
545 ItemInfo info = (ItemInfo) child.getTag();
546 if (info instanceof AppInfo || info instanceof ShortcutInfo) {
547 return res.getString(R.string.create_folder_with, info.title);
548 } else if (info instanceof FolderInfo) {
549 return res.getString(R.string.add_to_folder, info.title);
550 }
551 }
552 return "";
553 }
554
555 private String getConfirmationForIconDrop(int id) {
556 LauncherAccessibilityDelegate delegate =
557 LauncherAppState.getInstance().getAccessibilityDelegate();
558 LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
559
560 int y = id % mCountY;
561 int x = id / mCountY;
562
563 Resources res = getContext().getResources();
564 View child = getChildAt(x, y);
565 if (child == null || child == dragInfo.item) {
566 return res.getString(R.string.item_moved);
567 } else {
568 ItemInfo info = (ItemInfo) child.getTag();
569 if (info instanceof AppInfo || info instanceof ShortcutInfo) {
570 return res.getString(R.string.folder_created);
571
572 } else if (info instanceof FolderInfo) {
573 return res.getString(R.string.added_to_folder);
574 }
575 }
576 return "";
577 }
578
579 private Rect getItemBounds(int id) {
580 int cellY = id % mCountY;
581 int cellX = id / mCountY;
582 int x = getPaddingLeft() + (int) (cellX * (mCellWidth + mWidthGap));
583 int y = getPaddingTop() + (int) (cellY * (mCellHeight + mHeightGap));
584
585 Rect bounds = mTempRect;
586 bounds.set(x, y, x + mCellWidth, y + mCellHeight);
587 return bounds;
588 }
589 }
590
Chris Craik01f2d7f2013-10-01 14:41:56 -0700591 public void enableHardwareLayer(boolean hasLayer) {
592 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700593 }
594
595 public void buildHardwareLayer() {
596 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700597 }
598
Adam Cohen307fe232012-08-16 17:55:58 -0700599 public float getChildrenScale() {
600 return mIsHotseat ? mHotseatScale : 1.0f;
601 }
602
Winson Chung5f8afe62013-08-12 16:19:28 -0700603 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700604 mFixedCellWidth = mCellWidth = width;
605 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700606 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
607 mCountX, mCountY);
608 }
609
Adam Cohen2801caf2011-05-13 20:57:39 -0700610 public void setGridSize(int x, int y) {
611 mCountX = x;
612 mCountY = y;
613 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800614 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700615 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700616 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700617 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700618 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700619 }
620
Adam Cohen2374abf2013-04-16 14:56:57 -0700621 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
622 public void setInvertIfRtl(boolean invert) {
623 mShortcutsAndWidgets.setInvertIfRtl(invert);
624 }
625
Adam Cohen917e3882013-10-31 15:03:35 -0700626 public void setDropPending(boolean pending) {
627 mDropPending = pending;
628 }
629
630 public boolean isDropPending() {
631 return mDropPending;
632 }
633
Adam Cohenb5ba0972011-09-07 18:02:31 -0700634 void setOverScrollAmount(float r, boolean left) {
635 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
636 mOverScrollForegroundDrawable = mOverScrollLeft;
637 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
638 mOverScrollForegroundDrawable = mOverScrollRight;
639 }
640
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700641 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700642 mForegroundAlpha = (int) Math.round((r * 255));
643 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
644 invalidate();
645 }
646
Sunny Goyal508da152014-08-14 10:53:27 -0700647 void setPressedIcon(BubbleTextView icon, Bitmap background, int padding) {
648 if (icon == null || background == null) {
649 mTouchFeedbackView.setBitmap(null);
650 mTouchFeedbackView.animate().cancel();
651 } else {
652 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight()
653 - (mCountX * mCellWidth);
654 mTouchFeedbackView.setTranslationX(icon.getLeft() + (int) Math.ceil(offset / 2f)
655 - padding);
656 mTouchFeedbackView.setTranslationY(icon.getTop() - padding);
657 if (mTouchFeedbackView.setBitmap(background)) {
658 mTouchFeedbackView.setAlpha(0);
659 mTouchFeedbackView.animate().alpha(1)
660 .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
661 .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
662 .start();
663 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800664 }
665 }
666
Adam Cohendedbd962013-07-11 14:21:49 -0700667 void setUseActiveGlowBackground(boolean use) {
668 mUseActiveGlowBackground = use;
669 }
670
Winson Chung59a488a2013-12-10 12:32:14 -0800671 void disableBackground() {
672 mDrawBackground = false;
673 }
674
Adam Cohenc50438c2014-08-19 17:43:05 -0700675 void disableDragTarget() {
676 mIsDragTarget = false;
677 }
678
679 boolean isDragTarget() {
680 return mIsDragTarget;
681 }
682
683 void setIsDragOverlapping(boolean isDragOverlapping) {
684 if (mIsDragOverlapping != isDragOverlapping) {
685 mIsDragOverlapping = isDragOverlapping;
686 setUseActiveGlowBackground(mIsDragOverlapping);
687 invalidate();
688 }
689 }
690
Michael Jurka33945b22010-12-21 18:19:38 -0800691 boolean getIsDragOverlapping() {
692 return mIsDragOverlapping;
693 }
694
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700695 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700696 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700697 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
698 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
699 // When we're small, we are either drawn normally or in the "accepts drops" state (during
700 // a drag). However, we also drag the mini hover background *over* one of those two
701 // backgrounds
Winson Chung59a488a2013-12-10 12:32:14 -0800702 if (mDrawBackground && mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700703 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800704
Adam Cohendedbd962013-07-11 14:21:49 -0700705 if (mUseActiveGlowBackground) {
Michael Jurka33945b22010-12-21 18:19:38 -0800706 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700707 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700708 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700709 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700710 }
Michael Jurka33945b22010-12-21 18:19:38 -0800711
712 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
713 bg.setBounds(mBackgroundRect);
714 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700715 }
Romain Guya6abce82009-11-10 02:54:41 -0800716
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700717 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700718 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700719 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700720 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800721 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700722 mTempRect.set(r);
723 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700724 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700725 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700726 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700727 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700728 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800729
Adam Cohen482ed822012-03-02 14:15:13 -0800730 if (DEBUG_VISUALIZE_OCCUPIED) {
731 int[] pt = new int[2];
732 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700733 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800734 for (int i = 0; i < mCountX; i++) {
735 for (int j = 0; j < mCountY; j++) {
736 if (mOccupied[i][j]) {
737 cellToPoint(i, j, pt);
738 canvas.save();
739 canvas.translate(pt[0], pt[1]);
740 cd.draw(canvas);
741 canvas.restore();
742 }
743 }
744 }
745 }
746
Andrew Flynn850d2e72012-04-26 16:51:20 -0700747 int previewOffset = FolderRingAnimator.sPreviewSize;
748
Adam Cohen69ce2e52011-07-03 19:25:21 -0700749 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700750 LauncherAppState app = LauncherAppState.getInstance();
751 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700752 for (int i = 0; i < mFolderOuterRings.size(); i++) {
753 FolderRingAnimator fra = mFolderOuterRings.get(i);
754
Adam Cohen5108bc02013-09-20 17:04:51 -0700755 Drawable d;
756 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700757 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700758 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700759
Winson Chung89f97052013-09-20 11:32:26 -0700760 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700761 int centerX = mTempLocation[0] + mCellWidth / 2;
762 int centerY = mTempLocation[1] + previewOffset / 2 +
763 child.getPaddingTop() + grid.folderBackgroundOffset;
764
Adam Cohen5108bc02013-09-20 17:04:51 -0700765 // Draw outer ring, if it exists
766 if (FolderIcon.HAS_OUTER_RING) {
767 d = FolderRingAnimator.sSharedOuterRingDrawable;
768 width = (int) (fra.getOuterRingSize() * getChildrenScale());
769 height = width;
770 canvas.save();
771 canvas.translate(centerX - width / 2, centerY - height / 2);
772 d.setBounds(0, 0, width, height);
773 d.draw(canvas);
774 canvas.restore();
775 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700776
Winson Chung89f97052013-09-20 11:32:26 -0700777 // Draw inner ring
778 d = FolderRingAnimator.sSharedInnerRingDrawable;
779 width = (int) (fra.getInnerRingSize() * getChildrenScale());
780 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700781 canvas.save();
782 canvas.translate(centerX - width / 2, centerY - width / 2);
783 d.setBounds(0, 0, width, height);
784 d.draw(canvas);
785 canvas.restore();
786 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700787 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700788
789 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
790 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
791 int width = d.getIntrinsicWidth();
792 int height = d.getIntrinsicHeight();
793
794 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700795 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700796 if (child != null) {
797 int centerX = mTempLocation[0] + mCellWidth / 2;
798 int centerY = mTempLocation[1] + previewOffset / 2 +
799 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700800
Winson Chung89f97052013-09-20 11:32:26 -0700801 canvas.save();
802 canvas.translate(centerX - width / 2, centerY - width / 2);
803 d.setBounds(0, 0, width, height);
804 d.draw(canvas);
805 canvas.restore();
806 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700807 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700808 }
809
Adam Cohenb5ba0972011-09-07 18:02:31 -0700810 @Override
811 protected void dispatchDraw(Canvas canvas) {
812 super.dispatchDraw(canvas);
813 if (mForegroundAlpha > 0) {
814 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700815 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700816 }
817 }
818
Adam Cohen69ce2e52011-07-03 19:25:21 -0700819 public void showFolderAccept(FolderRingAnimator fra) {
820 mFolderOuterRings.add(fra);
821 }
822
823 public void hideFolderAccept(FolderRingAnimator fra) {
824 if (mFolderOuterRings.contains(fra)) {
825 mFolderOuterRings.remove(fra);
826 }
827 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700828 }
829
Adam Cohenc51934b2011-07-26 21:07:43 -0700830 public void setFolderLeaveBehindCell(int x, int y) {
831 mFolderLeaveBehindCell[0] = x;
832 mFolderLeaveBehindCell[1] = y;
833 invalidate();
834 }
835
836 public void clearFolderLeaveBehind() {
837 mFolderLeaveBehindCell[0] = -1;
838 mFolderLeaveBehindCell[1] = -1;
839 invalidate();
840 }
841
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700842 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700843 public boolean shouldDelayChildPressedState() {
844 return false;
845 }
846
Adam Cohen1462de32012-07-24 22:34:36 -0700847 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700848 try {
849 dispatchRestoreInstanceState(states);
850 } catch (IllegalArgumentException ex) {
851 if (LauncherAppState.isDogfoodBuild()) {
852 throw ex;
853 }
854 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
855 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
856 }
Adam Cohen1462de32012-07-24 22:34:36 -0700857 }
858
Michael Jurkae6235dd2011-10-04 15:02:05 -0700859 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700860 public void cancelLongPress() {
861 super.cancelLongPress();
862
863 // Cancel long press for all children
864 final int count = getChildCount();
865 for (int i = 0; i < count; i++) {
866 final View child = getChildAt(i);
867 child.cancelLongPress();
868 }
869 }
870
Michael Jurkadee05892010-07-27 10:01:56 -0700871 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
872 mInterceptTouchListener = listener;
873 }
874
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800875 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700876 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800877 }
878
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800879 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700880 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800881 }
882
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800883 public void setIsHotseat(boolean isHotseat) {
884 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700885 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800886 }
887
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800888 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Adam Cohen59400422014-03-05 18:07:04 -0800889 boolean markCells, boolean inLayout) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700890 final LayoutParams lp = params;
891
Andrew Flynnde38e422012-05-08 11:22:15 -0700892 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800893 if (child instanceof BubbleTextView) {
894 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700895 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800896 }
897
Adam Cohen307fe232012-08-16 17:55:58 -0700898 child.setScaleX(getChildrenScale());
899 child.setScaleY(getChildrenScale());
900
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800901 // Generate an id for each view, this assumes we have at most 256x256 cells
902 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700903 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700904 // If the horizontal or vertical span is set to -1, it is taken to
905 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700906 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
907 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908
Winson Chungaafa03c2010-06-11 17:34:16 -0700909 child.setId(childId);
Sunny Goyal1587d532015-01-29 09:57:16 -0800910 mShortcutsAndWidgets.addView(child, index, lp, inLayout);
Michael Jurkadee05892010-07-27 10:01:56 -0700911
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700912 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700913
Winson Chungaafa03c2010-06-11 17:34:16 -0700914 return true;
915 }
916 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800917 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700918
Adam Cohen59400422014-03-05 18:07:04 -0800919 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
920 boolean markCells) {
921 return addViewToCellLayout(child, index, childId, params, markCells, false);
922 }
923
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800924 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700925 public void removeAllViews() {
926 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700927 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700928 }
929
930 @Override
931 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700932 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700933 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700934 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700935 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700936 }
937
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700938 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700939 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700940 }
941
Michael Jurka0280c3b2010-09-17 15:00:07 -0700942 @Override
943 public void removeView(View view) {
944 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700945 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700946 }
947
948 @Override
949 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700950 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
951 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700952 }
953
954 @Override
955 public void removeViewInLayout(View view) {
956 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700957 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700958 }
959
960 @Override
961 public void removeViews(int start, int count) {
962 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700963 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700964 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700965 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700966 }
967
968 @Override
969 public void removeViewsInLayout(int start, int count) {
970 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700971 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700972 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700973 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800974 }
975
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700976 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700977 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800978 * @param x X coordinate of the point
979 * @param y Y coordinate of the point
980 * @param result Array of 2 ints to hold the x and y coordinate of the cell
981 */
982 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700983 final int hStartPadding = getPaddingLeft();
984 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800985
986 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
987 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
988
Adam Cohend22015c2010-07-26 22:02:18 -0700989 final int xAxis = mCountX;
990 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800991
992 if (result[0] < 0) result[0] = 0;
993 if (result[0] >= xAxis) result[0] = xAxis - 1;
994 if (result[1] < 0) result[1] = 0;
995 if (result[1] >= yAxis) result[1] = yAxis - 1;
996 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700997
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800998 /**
999 * Given a point, return the cell that most closely encloses that point
1000 * @param x X coordinate of the point
1001 * @param y Y coordinate of the point
1002 * @param result Array of 2 ints to hold the x and y coordinate of the cell
1003 */
1004 void pointToCellRounded(int x, int y, int[] result) {
1005 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
1006 }
1007
1008 /**
1009 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -07001010 *
1011 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001012 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -07001013 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001014 * @param result Array of 2 ints to hold the x and y coordinate of the point
1015 */
1016 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -07001017 final int hStartPadding = getPaddingLeft();
1018 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001019
1020 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
1021 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
1022 }
1023
Adam Cohene3e27a82011-04-15 12:07:39 -07001024 /**
Adam Cohen482ed822012-03-02 14:15:13 -08001025 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -07001026 *
1027 * @param cellX X coordinate of the cell
1028 * @param cellY Y coordinate of the cell
1029 *
1030 * @param result Array of 2 ints to hold the x and y coordinate of the point
1031 */
1032 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001033 regionToCenterPoint(cellX, cellY, 1, 1, result);
1034 }
1035
1036 /**
1037 * Given a cell coordinate and span return the point that represents the center of the regio
1038 *
1039 * @param cellX X coordinate of the cell
1040 * @param cellY Y coordinate of the cell
1041 *
1042 * @param result Array of 2 ints to hold the x and y coordinate of the point
1043 */
1044 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -07001045 final int hStartPadding = getPaddingLeft();
1046 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -07001047 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
1048 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
1049 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
1050 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -07001051 }
1052
Adam Cohen19f37922012-03-21 11:59:11 -07001053 /**
1054 * Given a cell coordinate and span fills out a corresponding pixel rect
1055 *
1056 * @param cellX X coordinate of the cell
1057 * @param cellY Y coordinate of the cell
1058 * @param result Rect in which to write the result
1059 */
1060 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
1061 final int hStartPadding = getPaddingLeft();
1062 final int vStartPadding = getPaddingTop();
1063 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
1064 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
1065 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
1066 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
1067 }
1068
Adam Cohen482ed822012-03-02 14:15:13 -08001069 public float getDistanceFromCell(float x, float y, int[] cell) {
1070 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
1071 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
1072 Math.pow(y - mTmpPoint[1], 2));
1073 return distance;
1074 }
1075
Romain Guy84f296c2009-11-04 15:00:44 -08001076 int getCellWidth() {
1077 return mCellWidth;
1078 }
1079
1080 int getCellHeight() {
1081 return mCellHeight;
1082 }
1083
Adam Cohend4844c32011-02-18 19:25:06 -08001084 int getWidthGap() {
1085 return mWidthGap;
1086 }
1087
1088 int getHeightGap() {
1089 return mHeightGap;
1090 }
1091
Adam Cohen7f4eabe2011-04-21 16:19:16 -07001092 Rect getContentRect(Rect r) {
1093 if (r == null) {
1094 r = new Rect();
1095 }
1096 int left = getPaddingLeft();
1097 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -07001098 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
1099 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -07001100 r.set(left, top, right, bottom);
1101 return r;
1102 }
1103
Winson Chungfe411c82013-09-26 16:07:17 -07001104 /** Return a rect that has the cellWidth/cellHeight (left, top), and
1105 * widthGap/heightGap (right, bottom) */
Winson Chung66700732013-08-20 16:56:15 -07001106 static void getMetrics(Rect metrics, int paddedMeasureWidth,
1107 int paddedMeasureHeight, int countX, int countY) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001108 LauncherAppState app = LauncherAppState.getInstance();
1109 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07001110 metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX),
1111 grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -07001112 }
1113
Adam Cohenf0f4eda2013-06-06 21:27:03 -07001114 public void setFixedSize(int width, int height) {
1115 mFixedWidth = width;
1116 mFixedHeight = height;
1117 }
1118
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001119 @Override
1120 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001121 LauncherAppState app = LauncherAppState.getInstance();
1122 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1123
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001124 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001125 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -07001126 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1127 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -07001128 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
1129 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -07001130 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -07001131 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
1132 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -07001133 if (cw != mCellWidth || ch != mCellHeight) {
1134 mCellWidth = cw;
1135 mCellHeight = ch;
1136 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
1137 mHeightGap, mCountX, mCountY);
1138 }
Winson Chung5f8afe62013-08-12 16:19:28 -07001139 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001140
Winson Chung2d75f122013-09-23 16:53:31 -07001141 int newWidth = childWidthSize;
1142 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -07001143 if (mFixedWidth > 0 && mFixedHeight > 0) {
1144 newWidth = mFixedWidth;
1145 newHeight = mFixedHeight;
1146 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001147 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
1148 }
1149
Adam Cohend22015c2010-07-26 22:02:18 -07001150 int numWidthGaps = mCountX - 1;
1151 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001152
Adam Cohen234c4cd2011-07-17 21:03:04 -07001153 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -07001154 int hSpace = childWidthSize;
1155 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -07001156 int hFreeSpace = hSpace - (mCountX * mCellWidth);
1157 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -07001158 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
1159 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -07001160 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
1161 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -07001162 } else {
1163 mWidthGap = mOriginalWidthGap;
1164 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -07001165 }
Michael Jurka8c920dd2011-01-20 14:16:56 -08001166 int count = getChildCount();
Winson Chung5f8afe62013-08-12 16:19:28 -07001167 int maxWidth = 0;
1168 int maxHeight = 0;
Michael Jurka8c920dd2011-01-20 14:16:56 -08001169 for (int i = 0; i < count; i++) {
1170 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -07001171 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
1172 MeasureSpec.EXACTLY);
1173 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
1174 MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -08001175 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -07001176 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
1177 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
Michael Jurka8c920dd2011-01-20 14:16:56 -08001178 }
Winson Chung2d75f122013-09-23 16:53:31 -07001179 if (mFixedWidth > 0 && mFixedHeight > 0) {
1180 setMeasuredDimension(maxWidth, maxHeight);
1181 } else {
1182 setMeasuredDimension(widthSize, heightSize);
1183 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001184 }
1185
1186 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001187 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -07001188 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
1189 (mCountX * mCellWidth);
1190 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
1191 int top = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001192 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001193 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001194 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -07001195 child.layout(left, top,
1196 left + r - l,
1197 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001198 }
1199 }
1200
1201 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001202 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1203 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -07001204
1205 // Expand the background drawing bounds by the padding baked into the background drawable
1206 Rect padding = new Rect();
1207 mNormalBackground.getPadding(padding);
1208 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
1209
Adam Cohenb5ba0972011-09-07 18:02:31 -07001210 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -07001211 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001212 }
1213
1214 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001215 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001216 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001217 }
1218
1219 @Override
1220 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001221 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001222 }
1223
Michael Jurka5f1c5092010-09-03 14:15:02 -07001224 public float getBackgroundAlpha() {
1225 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001226 }
1227
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001228 public void setBackgroundAlphaMultiplier(float multiplier) {
Adam Cohenc50438c2014-08-19 17:43:05 -07001229
Michael Jurkaa3d30ad2012-05-08 13:43:43 -07001230 if (mBackgroundAlphaMultiplier != multiplier) {
1231 mBackgroundAlphaMultiplier = multiplier;
1232 invalidate();
1233 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001234 }
1235
Adam Cohenddb82192010-11-10 16:32:54 -08001236 public float getBackgroundAlphaMultiplier() {
1237 return mBackgroundAlphaMultiplier;
1238 }
1239
Michael Jurka5f1c5092010-09-03 14:15:02 -07001240 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001241 if (mBackgroundAlpha != alpha) {
1242 mBackgroundAlpha = alpha;
1243 invalidate();
1244 }
Michael Jurkadee05892010-07-27 10:01:56 -07001245 }
1246
Michael Jurkaa52570f2012-03-20 03:18:20 -07001247 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -07001248 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -07001249 }
1250
Michael Jurkaa52570f2012-03-20 03:18:20 -07001251 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -07001252 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -07001253 }
1254
Patrick Dubroy440c3602010-07-13 17:50:32 -07001255 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001256 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001257 }
1258
Adam Cohen76fc0852011-06-17 13:26:23 -07001259 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001260 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001261 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001262 boolean[][] occupied = mOccupied;
1263 if (!permanent) {
1264 occupied = mTmpOccupied;
1265 }
1266
Adam Cohen19f37922012-03-21 11:59:11 -07001267 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001268 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1269 final ItemInfo info = (ItemInfo) child.getTag();
1270
1271 // We cancel any existing animations
1272 if (mReorderAnimators.containsKey(lp)) {
1273 mReorderAnimators.get(lp).cancel();
1274 mReorderAnimators.remove(lp);
1275 }
1276
Adam Cohen482ed822012-03-02 14:15:13 -08001277 final int oldX = lp.x;
1278 final int oldY = lp.y;
1279 if (adjustOccupied) {
1280 occupied[lp.cellX][lp.cellY] = false;
1281 occupied[cellX][cellY] = true;
1282 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001283 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001284 if (permanent) {
1285 lp.cellX = info.cellX = cellX;
1286 lp.cellY = info.cellY = cellY;
1287 } else {
1288 lp.tmpCellX = cellX;
1289 lp.tmpCellY = cellY;
1290 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001291 clc.setupLp(lp);
1292 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001293 final int newX = lp.x;
1294 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001295
Adam Cohen76fc0852011-06-17 13:26:23 -07001296 lp.x = oldX;
1297 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001298
Adam Cohen482ed822012-03-02 14:15:13 -08001299 // Exit early if we're not actually moving the view
1300 if (oldX == newX && oldY == newY) {
1301 lp.isLockedToGrid = true;
1302 return true;
1303 }
1304
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001305 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001306 va.setDuration(duration);
1307 mReorderAnimators.put(lp, va);
1308
1309 va.addUpdateListener(new AnimatorUpdateListener() {
1310 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001311 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001312 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001313 lp.x = (int) ((1 - r) * oldX + r * newX);
1314 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001315 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001316 }
1317 });
Adam Cohen482ed822012-03-02 14:15:13 -08001318 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001319 boolean cancelled = false;
1320 public void onAnimationEnd(Animator animation) {
1321 // If the animation was cancelled, it means that another animation
1322 // has interrupted this one, and we don't want to lock the item into
1323 // place just yet.
1324 if (!cancelled) {
1325 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001326 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001327 }
1328 if (mReorderAnimators.containsKey(lp)) {
1329 mReorderAnimators.remove(lp);
1330 }
1331 }
1332 public void onAnimationCancel(Animator animation) {
1333 cancelled = true;
1334 }
1335 });
Adam Cohen482ed822012-03-02 14:15:13 -08001336 va.setStartDelay(delay);
1337 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001338 return true;
1339 }
1340 return false;
1341 }
1342
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001343 /**
1344 * Estimate where the top left cell of the dragged item will land if it is dropped.
1345 *
1346 * @param originX The X value of the top left corner of the item
1347 * @param originY The Y value of the top left corner of the item
1348 * @param spanX The number of horizontal cells that the item spans
1349 * @param spanY The number of vertical cells that the item spans
1350 * @param result The estimated drop cell X and Y.
1351 */
1352 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001353 final int countX = mCountX;
1354 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001355
Michael Jurkaa63c4522010-08-19 13:52:27 -07001356 // pointToCellRounded takes the top left of a cell but will pad that with
1357 // cellWidth/2 and cellHeight/2 when finding the matching cell
1358 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001359
1360 // If the item isn't fully on this screen, snap to the edges
1361 int rightOverhang = result[0] + spanX - countX;
1362 if (rightOverhang > 0) {
1363 result[0] -= rightOverhang; // Snap to right
1364 }
1365 result[0] = Math.max(0, result[0]); // Snap to left
1366 int bottomOverhang = result[1] + spanY - countY;
1367 if (bottomOverhang > 0) {
1368 result[1] -= bottomOverhang; // Snap to bottom
1369 }
1370 result[1] = Math.max(0, result[1]); // Snap to top
1371 }
1372
Adam Cohen482ed822012-03-02 14:15:13 -08001373 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1374 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001375 final int oldDragCellX = mDragCell[0];
1376 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001377
Adam Cohen2801caf2011-05-13 20:57:39 -07001378 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001379 return;
1380 }
1381
Adam Cohen482ed822012-03-02 14:15:13 -08001382 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1383 mDragCell[0] = cellX;
1384 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001385 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001386 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001387 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001388
Joe Onorato4be866d2010-10-10 11:26:02 -07001389 int left = topLeft[0];
1390 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001391
Winson Chungb8c69f32011-10-19 21:36:08 -07001392 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001393 // When drawing the drag outline, it did not account for margin offsets
1394 // added by the view's parent.
1395 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1396 left += lp.leftMargin;
1397 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001398
Adam Cohen99e8b402011-03-25 19:23:43 -07001399 // Offsets due to the size difference between the View and the dragOutline.
1400 // There is a size difference to account for the outer blur, which may lie
1401 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001402 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001403 // We center about the x axis
1404 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1405 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001406 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001407 if (dragOffset != null && dragRegion != null) {
1408 // Center the drag region *horizontally* in the cell and apply a drag
1409 // outline offset
1410 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1411 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001412 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1413 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1414 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001415 } else {
1416 // Center the drag outline in the cell
1417 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1418 - dragOutline.getWidth()) / 2;
1419 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1420 - dragOutline.getHeight()) / 2;
1421 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001422 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001423 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001424 mDragOutlineAnims[oldIndex].animateOut();
1425 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001426 Rect r = mDragOutlines[mDragOutlineCurrent];
1427 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1428 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001429 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001430 }
Winson Chung150fbab2010-09-29 17:14:26 -07001431
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001432 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1433 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001434 }
1435 }
1436
Adam Cohene0310962011-04-18 16:15:31 -07001437 public void clearDragOutlines() {
1438 final int oldIndex = mDragOutlineCurrent;
1439 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001440 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001441 }
1442
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001443 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001444 * Find a vacant area that will fit the given bounds nearest the requested
1445 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001446 *
Romain Guy51afc022009-05-04 18:03:43 -07001447 * @param pixelX The X location at which you want to search for a vacant area.
1448 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001449 * @param spanX Horizontal span of the object.
1450 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001451 * @param result Array in which to place the result, or null (in which case a new array will
1452 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001453 * @return The X, Y cell of a vacant area that can contain this object,
1454 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001455 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001456 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1457 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001458 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001459 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001460
Michael Jurka6a1435d2010-09-27 17:35:12 -07001461 /**
1462 * Find a vacant area that will fit the given bounds nearest the requested
1463 * cell location. Uses Euclidean distance to score multiple vacant areas.
1464 *
1465 * @param pixelX The X location at which you want to search for a vacant area.
1466 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001467 * @param minSpanX The minimum horizontal span required
1468 * @param minSpanY The minimum vertical span required
1469 * @param spanX Horizontal span of the object.
1470 * @param spanY Vertical span of the object.
1471 * @param result Array in which to place the result, or null (in which case a new array will
1472 * be allocated)
1473 * @return The X, Y cell of a vacant area that can contain this object,
1474 * nearest the requested location.
1475 */
1476 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1477 int spanY, int[] result, int[] resultSpan) {
1478 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1479 result, resultSpan);
1480 }
1481
1482 /**
1483 * Find a vacant area that will fit the given bounds nearest the requested
1484 * cell location. Uses Euclidean distance to score multiple vacant areas.
1485 *
1486 * @param pixelX The X location at which you want to search for a vacant area.
1487 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001488 * @param spanX Horizontal span of the object.
1489 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001490 * @param ignoreOccupied If true, the result can be an occupied cell
1491 * @param result Array in which to place the result, or null (in which case a new array will
1492 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001493 * @return The X, Y cell of a vacant area that can contain this object,
1494 * nearest the requested location.
1495 */
Adam Cohendf035382011-04-11 17:22:04 -07001496 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1497 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001498 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001499 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001500 }
1501
1502 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1503 private void lazyInitTempRectStack() {
1504 if (mTempRectStack.isEmpty()) {
1505 for (int i = 0; i < mCountX * mCountY; i++) {
1506 mTempRectStack.push(new Rect());
1507 }
1508 }
1509 }
Adam Cohen482ed822012-03-02 14:15:13 -08001510
Adam Cohend41fbf52012-02-16 23:53:59 -08001511 private void recycleTempRects(Stack<Rect> used) {
1512 while (!used.isEmpty()) {
1513 mTempRectStack.push(used.pop());
1514 }
1515 }
1516
1517 /**
1518 * Find a vacant area that will fit the given bounds nearest the requested
1519 * cell location. Uses Euclidean distance to score multiple vacant areas.
1520 *
1521 * @param pixelX The X location at which you want to search for a vacant area.
1522 * @param pixelY The Y location at which you want to search for a vacant area.
1523 * @param minSpanX The minimum horizontal span required
1524 * @param minSpanY The minimum vertical span required
1525 * @param spanX Horizontal span of the object.
1526 * @param spanY Vertical span of the object.
1527 * @param ignoreOccupied If true, the result can be an occupied cell
1528 * @param result Array in which to place the result, or null (in which case a new array will
1529 * be allocated)
1530 * @return The X, Y cell of a vacant area that can contain this object,
1531 * nearest the requested location.
1532 */
1533 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001534 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1535 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001536 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001537 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001538 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001539
Adam Cohene3e27a82011-04-15 12:07:39 -07001540 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1541 // to the center of the item, but we are searching based on the top-left cell, so
1542 // we translate the point over to correspond to the top-left.
1543 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1544 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1545
Jeff Sharkey70864282009-04-07 21:08:40 -07001546 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001547 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001548 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001549 final Rect bestRect = new Rect(-1, -1, -1, -1);
1550 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001551
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001552 final int countX = mCountX;
1553 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001554
Adam Cohend41fbf52012-02-16 23:53:59 -08001555 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1556 spanX < minSpanX || spanY < minSpanY) {
1557 return bestXY;
1558 }
1559
1560 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001561 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001562 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1563 int ySize = -1;
1564 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001565 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001566 // First, let's see if this thing fits anywhere
1567 for (int i = 0; i < minSpanX; i++) {
1568 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001569 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001570 continue inner;
1571 }
Michael Jurkac28de512010-08-13 11:27:44 -07001572 }
1573 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001574 xSize = minSpanX;
1575 ySize = minSpanY;
1576
1577 // We know that the item will fit at _some_ acceptable size, now let's see
1578 // how big we can make it. We'll alternate between incrementing x and y spans
1579 // until we hit a limit.
1580 boolean incX = true;
1581 boolean hitMaxX = xSize >= spanX;
1582 boolean hitMaxY = ySize >= spanY;
1583 while (!(hitMaxX && hitMaxY)) {
1584 if (incX && !hitMaxX) {
1585 for (int j = 0; j < ySize; j++) {
1586 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1587 // We can't move out horizontally
1588 hitMaxX = true;
1589 }
1590 }
1591 if (!hitMaxX) {
1592 xSize++;
1593 }
1594 } else if (!hitMaxY) {
1595 for (int i = 0; i < xSize; i++) {
1596 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1597 // We can't move out vertically
1598 hitMaxY = true;
1599 }
1600 }
1601 if (!hitMaxY) {
1602 ySize++;
1603 }
1604 }
1605 hitMaxX |= xSize >= spanX;
1606 hitMaxY |= ySize >= spanY;
1607 incX = !incX;
1608 }
1609 incX = true;
1610 hitMaxX = xSize >= spanX;
1611 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001612 }
Winson Chung0be025d2011-05-23 17:45:09 -07001613 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001614 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001615
Adam Cohend41fbf52012-02-16 23:53:59 -08001616 // We verify that the current rect is not a sub-rect of any of our previous
1617 // candidates. In this case, the current rect is disqualified in favour of the
1618 // containing rect.
1619 Rect currentRect = mTempRectStack.pop();
1620 currentRect.set(x, y, x + xSize, y + ySize);
1621 boolean contained = false;
1622 for (Rect r : validRegions) {
1623 if (r.contains(currentRect)) {
1624 contained = true;
1625 break;
1626 }
1627 }
1628 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001629 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1630 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001631
Adam Cohend41fbf52012-02-16 23:53:59 -08001632 if ((distance <= bestDistance && !contained) ||
1633 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001634 bestDistance = distance;
1635 bestXY[0] = x;
1636 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001637 if (resultSpan != null) {
1638 resultSpan[0] = xSize;
1639 resultSpan[1] = ySize;
1640 }
1641 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001642 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001643 }
1644 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001645 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001646 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001647
Adam Cohenc0dcf592011-06-01 15:30:43 -07001648 // Return -1, -1 if no suitable location found
1649 if (bestDistance == Double.MAX_VALUE) {
1650 bestXY[0] = -1;
1651 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001652 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001653 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001654 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001655 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001656
Adam Cohen482ed822012-03-02 14:15:13 -08001657 /**
1658 * Find a vacant area that will fit the given bounds nearest the requested
1659 * cell location, and will also weigh in a suggested direction vector of the
1660 * desired location. This method computers distance based on unit grid distances,
1661 * not pixel distances.
1662 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001663 * @param cellX The X cell nearest to which you want to search for a vacant area.
1664 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001665 * @param spanX Horizontal span of the object.
1666 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001667 * @param direction The favored direction in which the views should move from x, y
1668 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1669 * matches exactly. Otherwise we find the best matching direction.
1670 * @param occoupied The array which represents which cells in the CellLayout are occupied
1671 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001672 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001673 * @param result Array in which to place the result, or null (in which case a new array will
1674 * be allocated)
1675 * @return The X, Y cell of a vacant area that can contain this object,
1676 * nearest the requested location.
1677 */
1678 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001679 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001680 // Keep track of best-scoring drop area
1681 final int[] bestXY = result != null ? result : new int[2];
1682 float bestDistance = Float.MAX_VALUE;
1683 int bestDirectionScore = Integer.MIN_VALUE;
1684
1685 final int countX = mCountX;
1686 final int countY = mCountY;
1687
1688 for (int y = 0; y < countY - (spanY - 1); y++) {
1689 inner:
1690 for (int x = 0; x < countX - (spanX - 1); x++) {
1691 // First, let's see if this thing fits anywhere
1692 for (int i = 0; i < spanX; i++) {
1693 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001694 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001695 continue inner;
1696 }
1697 }
1698 }
1699
1700 float distance = (float)
1701 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1702 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001703 computeDirectionVector(x - cellX, y - cellY, curDirection);
1704 // The direction score is just the dot product of the two candidate direction
1705 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001706 int curDirectionScore = direction[0] * curDirection[0] +
1707 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001708 boolean exactDirectionOnly = false;
1709 boolean directionMatches = direction[0] == curDirection[0] &&
1710 direction[0] == curDirection[0];
1711 if ((directionMatches || !exactDirectionOnly) &&
1712 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001713 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1714 bestDistance = distance;
1715 bestDirectionScore = curDirectionScore;
1716 bestXY[0] = x;
1717 bestXY[1] = y;
1718 }
1719 }
1720 }
1721
1722 // Return -1, -1 if no suitable location found
1723 if (bestDistance == Float.MAX_VALUE) {
1724 bestXY[0] = -1;
1725 bestXY[1] = -1;
1726 }
1727 return bestXY;
1728 }
1729
1730 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001731 int[] direction, ItemConfiguration currentState) {
1732 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001733 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001734 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001735 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1736
Adam Cohen8baab352012-03-20 17:39:21 -07001737 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001738
1739 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001740 c.x = mTempLocation[0];
1741 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001742 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001743 }
Adam Cohen8baab352012-03-20 17:39:21 -07001744 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001745 return success;
1746 }
1747
Adam Cohenf3900c22012-11-16 18:28:11 -08001748 /**
1749 * This helper class defines a cluster of views. It helps with defining complex edges
1750 * of the cluster and determining how those edges interact with other views. The edges
1751 * essentially define a fine-grained boundary around the cluster of views -- like a more
1752 * precise version of a bounding box.
1753 */
1754 private class ViewCluster {
1755 final static int LEFT = 0;
1756 final static int TOP = 1;
1757 final static int RIGHT = 2;
1758 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001759
Adam Cohenf3900c22012-11-16 18:28:11 -08001760 ArrayList<View> views;
1761 ItemConfiguration config;
1762 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001763
Adam Cohenf3900c22012-11-16 18:28:11 -08001764 int[] leftEdge = new int[mCountY];
1765 int[] rightEdge = new int[mCountY];
1766 int[] topEdge = new int[mCountX];
1767 int[] bottomEdge = new int[mCountX];
1768 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1769
1770 @SuppressWarnings("unchecked")
1771 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1772 this.views = (ArrayList<View>) views.clone();
1773 this.config = config;
1774 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001775 }
1776
Adam Cohenf3900c22012-11-16 18:28:11 -08001777 void resetEdges() {
1778 for (int i = 0; i < mCountX; i++) {
1779 topEdge[i] = -1;
1780 bottomEdge[i] = -1;
1781 }
1782 for (int i = 0; i < mCountY; i++) {
1783 leftEdge[i] = -1;
1784 rightEdge[i] = -1;
1785 }
1786 leftEdgeDirty = true;
1787 rightEdgeDirty = true;
1788 bottomEdgeDirty = true;
1789 topEdgeDirty = true;
1790 boundingRectDirty = true;
1791 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001792
Adam Cohenf3900c22012-11-16 18:28:11 -08001793 void computeEdge(int which, int[] edge) {
1794 int count = views.size();
1795 for (int i = 0; i < count; i++) {
1796 CellAndSpan cs = config.map.get(views.get(i));
1797 switch (which) {
1798 case LEFT:
1799 int left = cs.x;
1800 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1801 if (left < edge[j] || edge[j] < 0) {
1802 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001803 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001804 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001805 break;
1806 case RIGHT:
1807 int right = cs.x + cs.spanX;
1808 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1809 if (right > edge[j]) {
1810 edge[j] = right;
1811 }
1812 }
1813 break;
1814 case TOP:
1815 int top = cs.y;
1816 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1817 if (top < edge[j] || edge[j] < 0) {
1818 edge[j] = top;
1819 }
1820 }
1821 break;
1822 case BOTTOM:
1823 int bottom = cs.y + cs.spanY;
1824 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1825 if (bottom > edge[j]) {
1826 edge[j] = bottom;
1827 }
1828 }
1829 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001830 }
1831 }
1832 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001833
1834 boolean isViewTouchingEdge(View v, int whichEdge) {
1835 CellAndSpan cs = config.map.get(v);
1836
1837 int[] edge = getEdge(whichEdge);
1838
1839 switch (whichEdge) {
1840 case LEFT:
1841 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1842 if (edge[i] == cs.x + cs.spanX) {
1843 return true;
1844 }
1845 }
1846 break;
1847 case RIGHT:
1848 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1849 if (edge[i] == cs.x) {
1850 return true;
1851 }
1852 }
1853 break;
1854 case TOP:
1855 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1856 if (edge[i] == cs.y + cs.spanY) {
1857 return true;
1858 }
1859 }
1860 break;
1861 case BOTTOM:
1862 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1863 if (edge[i] == cs.y) {
1864 return true;
1865 }
1866 }
1867 break;
1868 }
1869 return false;
1870 }
1871
1872 void shift(int whichEdge, int delta) {
1873 for (View v: views) {
1874 CellAndSpan c = config.map.get(v);
1875 switch (whichEdge) {
1876 case LEFT:
1877 c.x -= delta;
1878 break;
1879 case RIGHT:
1880 c.x += delta;
1881 break;
1882 case TOP:
1883 c.y -= delta;
1884 break;
1885 case BOTTOM:
1886 default:
1887 c.y += delta;
1888 break;
1889 }
1890 }
1891 resetEdges();
1892 }
1893
1894 public void addView(View v) {
1895 views.add(v);
1896 resetEdges();
1897 }
1898
1899 public Rect getBoundingRect() {
1900 if (boundingRectDirty) {
1901 boolean first = true;
1902 for (View v: views) {
1903 CellAndSpan c = config.map.get(v);
1904 if (first) {
1905 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1906 first = false;
1907 } else {
1908 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1909 }
1910 }
1911 }
1912 return boundingRect;
1913 }
1914
1915 public int[] getEdge(int which) {
1916 switch (which) {
1917 case LEFT:
1918 return getLeftEdge();
1919 case RIGHT:
1920 return getRightEdge();
1921 case TOP:
1922 return getTopEdge();
1923 case BOTTOM:
1924 default:
1925 return getBottomEdge();
1926 }
1927 }
1928
1929 public int[] getLeftEdge() {
1930 if (leftEdgeDirty) {
1931 computeEdge(LEFT, leftEdge);
1932 }
1933 return leftEdge;
1934 }
1935
1936 public int[] getRightEdge() {
1937 if (rightEdgeDirty) {
1938 computeEdge(RIGHT, rightEdge);
1939 }
1940 return rightEdge;
1941 }
1942
1943 public int[] getTopEdge() {
1944 if (topEdgeDirty) {
1945 computeEdge(TOP, topEdge);
1946 }
1947 return topEdge;
1948 }
1949
1950 public int[] getBottomEdge() {
1951 if (bottomEdgeDirty) {
1952 computeEdge(BOTTOM, bottomEdge);
1953 }
1954 return bottomEdge;
1955 }
1956
1957 PositionComparator comparator = new PositionComparator();
1958 class PositionComparator implements Comparator<View> {
1959 int whichEdge = 0;
1960 public int compare(View left, View right) {
1961 CellAndSpan l = config.map.get(left);
1962 CellAndSpan r = config.map.get(right);
1963 switch (whichEdge) {
1964 case LEFT:
1965 return (r.x + r.spanX) - (l.x + l.spanX);
1966 case RIGHT:
1967 return l.x - r.x;
1968 case TOP:
1969 return (r.y + r.spanY) - (l.y + l.spanY);
1970 case BOTTOM:
1971 default:
1972 return l.y - r.y;
1973 }
1974 }
1975 }
1976
1977 public void sortConfigurationForEdgePush(int edge) {
1978 comparator.whichEdge = edge;
1979 Collections.sort(config.sortedViews, comparator);
1980 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001981 }
1982
Adam Cohenf3900c22012-11-16 18:28:11 -08001983 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1984 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001985
Adam Cohenf3900c22012-11-16 18:28:11 -08001986 ViewCluster cluster = new ViewCluster(views, currentState);
1987 Rect clusterRect = cluster.getBoundingRect();
1988 int whichEdge;
1989 int pushDistance;
1990 boolean fail = false;
1991
1992 // Determine the edge of the cluster that will be leading the push and how far
1993 // the cluster must be shifted.
1994 if (direction[0] < 0) {
1995 whichEdge = ViewCluster.LEFT;
1996 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001997 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001998 whichEdge = ViewCluster.RIGHT;
1999 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
2000 } else if (direction[1] < 0) {
2001 whichEdge = ViewCluster.TOP;
2002 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
2003 } else {
2004 whichEdge = ViewCluster.BOTTOM;
2005 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07002006 }
2007
Adam Cohenf3900c22012-11-16 18:28:11 -08002008 // Break early for invalid push distance.
2009 if (pushDistance <= 0) {
2010 return false;
Adam Cohene0489502012-08-27 15:18:53 -07002011 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002012
2013 // Mark the occupied state as false for the group of views we want to move.
2014 for (View v: views) {
2015 CellAndSpan c = currentState.map.get(v);
2016 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
2017 }
2018
2019 // We save the current configuration -- if we fail to find a solution we will revert
2020 // to the initial state. The process of finding a solution modifies the configuration
2021 // in place, hence the need for revert in the failure case.
2022 currentState.save();
2023
2024 // The pushing algorithm is simplified by considering the views in the order in which
2025 // they would be pushed by the cluster. For example, if the cluster is leading with its
2026 // left edge, we consider sort the views by their right edge, from right to left.
2027 cluster.sortConfigurationForEdgePush(whichEdge);
2028
2029 while (pushDistance > 0 && !fail) {
2030 for (View v: currentState.sortedViews) {
2031 // For each view that isn't in the cluster, we see if the leading edge of the
2032 // cluster is contacting the edge of that view. If so, we add that view to the
2033 // cluster.
2034 if (!cluster.views.contains(v) && v != dragView) {
2035 if (cluster.isViewTouchingEdge(v, whichEdge)) {
2036 LayoutParams lp = (LayoutParams) v.getLayoutParams();
2037 if (!lp.canReorder) {
2038 // The push solution includes the all apps button, this is not viable.
2039 fail = true;
2040 break;
2041 }
2042 cluster.addView(v);
2043 CellAndSpan c = currentState.map.get(v);
2044
2045 // Adding view to cluster, mark it as not occupied.
2046 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
2047 }
2048 }
2049 }
2050 pushDistance--;
2051
2052 // The cluster has been completed, now we move the whole thing over in the appropriate
2053 // direction.
2054 cluster.shift(whichEdge, 1);
2055 }
2056
2057 boolean foundSolution = false;
2058 clusterRect = cluster.getBoundingRect();
2059
2060 // Due to the nature of the algorithm, the only check required to verify a valid solution
2061 // is to ensure that completed shifted cluster lies completely within the cell layout.
2062 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
2063 clusterRect.bottom <= mCountY) {
2064 foundSolution = true;
2065 } else {
2066 currentState.restore();
2067 }
2068
2069 // In either case, we set the occupied array as marked for the location of the views
2070 for (View v: cluster.views) {
2071 CellAndSpan c = currentState.map.get(v);
2072 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
2073 }
2074
2075 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07002076 }
2077
Adam Cohen482ed822012-03-02 14:15:13 -08002078 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08002079 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08002080 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08002081
Adam Cohen8baab352012-03-20 17:39:21 -07002082 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08002083 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07002084 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08002085 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07002086 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08002087 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07002088 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002089 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002090 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002091 }
2092 }
Adam Cohen8baab352012-03-20 17:39:21 -07002093
Adam Cohen8baab352012-03-20 17:39:21 -07002094 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08002095 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07002096 CellAndSpan c = currentState.map.get(v);
2097 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
2098 }
2099
Adam Cohen47a876d2012-03-19 13:21:41 -07002100 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
2101 int top = boundingRect.top;
2102 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07002103 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07002104 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08002105 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07002106 CellAndSpan c = currentState.map.get(v);
2107 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07002108 }
2109
Adam Cohen482ed822012-03-02 14:15:13 -08002110 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
2111
Adam Cohenf3900c22012-11-16 18:28:11 -08002112 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
2113 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08002114
Adam Cohen8baab352012-03-20 17:39:21 -07002115 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08002116 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07002117 int deltaX = mTempLocation[0] - boundingRect.left;
2118 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08002119 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07002120 CellAndSpan c = currentState.map.get(v);
2121 c.x += deltaX;
2122 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08002123 }
2124 success = true;
2125 }
Adam Cohen8baab352012-03-20 17:39:21 -07002126
2127 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08002128 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07002129 CellAndSpan c = currentState.map.get(v);
2130 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002131 }
2132 return success;
2133 }
2134
2135 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
2136 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
2137 }
2138
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002139 // This method tries to find a reordering solution which satisfies the push mechanic by trying
2140 // to push items in each of the cardinal directions, in an order based on the direction vector
2141 // passed.
2142 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
2143 int[] direction, View ignoreView, ItemConfiguration solution) {
2144 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002145 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002146 // separately in each of the components.
2147 int temp = direction[1];
2148 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002149
2150 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002151 ignoreView, solution)) {
2152 return true;
2153 }
2154 direction[1] = temp;
2155 temp = direction[0];
2156 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002157
2158 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002159 ignoreView, solution)) {
2160 return true;
2161 }
2162 // Revert the direction
2163 direction[0] = temp;
2164
2165 // Now we try pushing in each component of the opposite direction
2166 direction[0] *= -1;
2167 direction[1] *= -1;
2168 temp = direction[1];
2169 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002170 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002171 ignoreView, solution)) {
2172 return true;
2173 }
2174
2175 direction[1] = temp;
2176 temp = direction[0];
2177 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002178 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002179 ignoreView, solution)) {
2180 return true;
2181 }
2182 // revert the direction
2183 direction[0] = temp;
2184 direction[0] *= -1;
2185 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07002186
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002187 } else {
2188 // If the direction vector has a single non-zero component, we push first in the
2189 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08002190 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002191 ignoreView, solution)) {
2192 return true;
2193 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002194 // Then we try the opposite direction
2195 direction[0] *= -1;
2196 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002197 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002198 ignoreView, solution)) {
2199 return true;
2200 }
2201 // Switch the direction back
2202 direction[0] *= -1;
2203 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07002204
2205 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002206 // to find a solution by pushing along the perpendicular axis.
2207
2208 // Swap the components
2209 int temp = direction[1];
2210 direction[1] = direction[0];
2211 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08002212 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002213 ignoreView, solution)) {
2214 return true;
2215 }
2216
2217 // Then we try the opposite direction
2218 direction[0] *= -1;
2219 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002220 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002221 ignoreView, solution)) {
2222 return true;
2223 }
2224 // Switch the direction back
2225 direction[0] *= -1;
2226 direction[1] *= -1;
2227
2228 // Swap the components back
2229 temp = direction[1];
2230 direction[1] = direction[0];
2231 direction[0] = temp;
2232 }
2233 return false;
2234 }
2235
Adam Cohen482ed822012-03-02 14:15:13 -08002236 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07002237 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07002238 // Return early if get invalid cell positions
2239 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08002240
Adam Cohen8baab352012-03-20 17:39:21 -07002241 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08002242 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002243
Adam Cohen8baab352012-03-20 17:39:21 -07002244 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08002245 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07002246 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07002247 if (c != null) {
2248 c.x = cellX;
2249 c.y = cellY;
2250 }
Adam Cohen482ed822012-03-02 14:15:13 -08002251 }
Adam Cohen482ed822012-03-02 14:15:13 -08002252 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2253 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07002254 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08002255 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002256 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002257 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002258 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002259 if (Rect.intersects(r0, r1)) {
2260 if (!lp.canReorder) {
2261 return false;
2262 }
2263 mIntersectingViews.add(child);
2264 }
2265 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002266
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002267 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
2268
Winson Chung5f8afe62013-08-12 16:19:28 -07002269 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002270 // we try to find a solution such that no displaced item travels through another item
2271 // without also displacing that item.
2272 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002273 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07002274 return true;
2275 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002276
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002277 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08002278 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002279 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002280 return true;
2281 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002282
Adam Cohen482ed822012-03-02 14:15:13 -08002283 // Ok, they couldn't move as a block, let's move them individually
2284 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07002285 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002286 return false;
2287 }
2288 }
2289 return true;
2290 }
2291
2292 /*
2293 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2294 * the provided point and the provided cell
2295 */
Adam Cohen47a876d2012-03-19 13:21:41 -07002296 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08002297 double angle = Math.atan(((float) deltaY) / deltaX);
2298
2299 result[0] = 0;
2300 result[1] = 0;
2301 if (Math.abs(Math.cos(angle)) > 0.5f) {
2302 result[0] = (int) Math.signum(deltaX);
2303 }
2304 if (Math.abs(Math.sin(angle)) > 0.5f) {
2305 result[1] = (int) Math.signum(deltaY);
2306 }
2307 }
2308
Adam Cohen8baab352012-03-20 17:39:21 -07002309 private void copyOccupiedArray(boolean[][] occupied) {
2310 for (int i = 0; i < mCountX; i++) {
2311 for (int j = 0; j < mCountY; j++) {
2312 occupied[i][j] = mOccupied[i][j];
2313 }
2314 }
2315 }
2316
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002317 ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
2318 int spanX, int spanY, int[] direction, View dragView, boolean decX,
2319 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07002320 // Copy the current state into the solution. This solution will be manipulated as necessary.
2321 copyCurrentStateToSolution(solution, false);
2322 // Copy the current occupied array into the temporary occupied array. This array will be
2323 // manipulated as necessary to find a solution.
2324 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08002325
2326 // We find the nearest cell into which we would place the dragged item, assuming there's
2327 // nothing in its way.
2328 int result[] = new int[2];
2329 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2330
2331 boolean success = false;
2332 // First we try the exact nearest position of the item being dragged,
2333 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002334 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2335 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002336
2337 if (!success) {
2338 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2339 // x, then 1 in y etc.
2340 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002341 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2342 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002343 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002344 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2345 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002346 }
2347 solution.isSolution = false;
2348 } else {
2349 solution.isSolution = true;
2350 solution.dragViewX = result[0];
2351 solution.dragViewY = result[1];
2352 solution.dragViewSpanX = spanX;
2353 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002354 }
2355 return solution;
2356 }
2357
2358 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002359 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002360 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002361 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002362 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002363 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002364 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002365 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002366 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002367 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002368 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002369 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002370 }
2371 }
2372
2373 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2374 for (int i = 0; i < mCountX; i++) {
2375 for (int j = 0; j < mCountY; j++) {
2376 mTmpOccupied[i][j] = false;
2377 }
2378 }
2379
Michael Jurkaa52570f2012-03-20 03:18:20 -07002380 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002381 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002382 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002383 if (child == dragView) continue;
2384 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002385 CellAndSpan c = solution.map.get(child);
2386 if (c != null) {
2387 lp.tmpCellX = c.x;
2388 lp.tmpCellY = c.y;
2389 lp.cellHSpan = c.spanX;
2390 lp.cellVSpan = c.spanY;
2391 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002392 }
2393 }
2394 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2395 solution.dragViewSpanY, mTmpOccupied, true);
2396 }
2397
2398 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2399 commitDragView) {
2400
2401 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2402 for (int i = 0; i < mCountX; i++) {
2403 for (int j = 0; j < mCountY; j++) {
2404 occupied[i][j] = false;
2405 }
2406 }
2407
Michael Jurkaa52570f2012-03-20 03:18:20 -07002408 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002409 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002410 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002411 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002412 CellAndSpan c = solution.map.get(child);
2413 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002414 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2415 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002416 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002417 }
2418 }
2419 if (commitDragView) {
2420 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2421 solution.dragViewSpanY, occupied, true);
2422 }
2423 }
2424
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002425
2426 // This method starts or changes the reorder preview animations
2427 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2428 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002429 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002430 for (int i = 0; i < childCount; i++) {
2431 View child = mShortcutsAndWidgets.getChildAt(i);
2432 if (child == dragView) continue;
2433 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002434 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2435 != null && !solution.intersectingViews.contains(child);
2436
Adam Cohen19f37922012-03-21 11:59:11 -07002437 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002438 if (c != null && !skip) {
2439 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2440 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002441 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002442 }
2443 }
2444 }
2445
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002446 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002447 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002448 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002449 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002450 float finalDeltaX;
2451 float finalDeltaY;
2452 float initDeltaX;
2453 float initDeltaY;
2454 float finalScale;
2455 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002456 int mode;
2457 boolean repeating = false;
2458 private static final int PREVIEW_DURATION = 300;
2459 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2460
2461 public static final int MODE_HINT = 0;
2462 public static final int MODE_PREVIEW = 1;
2463
Adam Cohene7587d22012-05-24 18:50:02 -07002464 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002465
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002466 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2467 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002468 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2469 final int x0 = mTmpPoint[0];
2470 final int y0 = mTmpPoint[1];
2471 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2472 final int x1 = mTmpPoint[0];
2473 final int y1 = mTmpPoint[1];
2474 final int dX = x1 - x0;
2475 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002476 finalDeltaX = 0;
2477 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002478 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002479 if (dX == dY && dX == 0) {
2480 } else {
2481 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002482 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002483 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002484 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002485 } else {
2486 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002487 finalDeltaX = (int) (- dir * Math.signum(dX) *
2488 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2489 finalDeltaY = (int) (- dir * Math.signum(dY) *
2490 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002491 }
2492 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002493 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002494 initDeltaX = child.getTranslationX();
2495 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002496 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002497 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002498 this.child = child;
2499 }
2500
Adam Cohend024f982012-05-23 18:26:45 -07002501 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002502 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002503 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002504 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002505 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002506 if (finalDeltaX == 0 && finalDeltaY == 0) {
2507 completeAnimationImmediately();
2508 return;
2509 }
Adam Cohen19f37922012-03-21 11:59:11 -07002510 }
Adam Cohend024f982012-05-23 18:26:45 -07002511 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002512 return;
2513 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002514 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002515 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002516 va.setRepeatMode(ValueAnimator.REVERSE);
2517 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002518 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002519 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002520 va.addUpdateListener(new AnimatorUpdateListener() {
2521 @Override
2522 public void onAnimationUpdate(ValueAnimator animation) {
2523 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002524 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2525 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2526 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002527 child.setTranslationX(x);
2528 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002529 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002530 child.setScaleX(s);
2531 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002532 }
2533 });
2534 va.addListener(new AnimatorListenerAdapter() {
2535 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002536 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002537 initDeltaX = 0;
2538 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002539 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002540 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002541 }
2542 });
Adam Cohen19f37922012-03-21 11:59:11 -07002543 mShakeAnimators.put(child, this);
2544 va.start();
2545 }
2546
Adam Cohend024f982012-05-23 18:26:45 -07002547 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002548 if (a != null) {
2549 a.cancel();
2550 }
Adam Cohen19f37922012-03-21 11:59:11 -07002551 }
Adam Cohene7587d22012-05-24 18:50:02 -07002552
Brandon Keely50e6e562012-05-08 16:28:49 -07002553 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002554 if (a != null) {
2555 a.cancel();
2556 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002557
Michael Jurka2ecf9952012-06-18 12:52:28 -07002558 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002559 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002560 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002561 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2562 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002563 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2564 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002565 );
2566 s.setDuration(REORDER_ANIMATION_DURATION);
2567 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2568 s.start();
2569 }
Adam Cohen19f37922012-03-21 11:59:11 -07002570 }
2571
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002572 private void completeAndClearReorderPreviewAnimations() {
2573 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002574 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002575 }
2576 mShakeAnimators.clear();
2577 }
2578
Adam Cohen482ed822012-03-02 14:15:13 -08002579 private void commitTempPlacement() {
2580 for (int i = 0; i < mCountX; i++) {
2581 for (int j = 0; j < mCountY; j++) {
2582 mOccupied[i][j] = mTmpOccupied[i][j];
2583 }
2584 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002585 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002586 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002587 View child = mShortcutsAndWidgets.getChildAt(i);
2588 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2589 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002590 // We do a null check here because the item info can be null in the case of the
2591 // AllApps button in the hotseat.
2592 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002593 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2594 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2595 info.requiresDbUpdate = true;
2596 }
Adam Cohen2acce882012-03-28 19:03:19 -07002597 info.cellX = lp.cellX = lp.tmpCellX;
2598 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002599 info.spanX = lp.cellHSpan;
2600 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002601 }
Adam Cohen482ed822012-03-02 14:15:13 -08002602 }
Adam Cohen2acce882012-03-28 19:03:19 -07002603 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002604 }
2605
2606 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002607 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002608 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002609 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002610 lp.useTmpCoords = useTempCoords;
2611 }
2612 }
2613
Adam Cohen482ed822012-03-02 14:15:13 -08002614 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2615 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2616 int[] result = new int[2];
2617 int[] resultSpan = new int[2];
2618 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2619 resultSpan);
2620 if (result[0] >= 0 && result[1] >= 0) {
2621 copyCurrentStateToSolution(solution, false);
2622 solution.dragViewX = result[0];
2623 solution.dragViewY = result[1];
2624 solution.dragViewSpanX = resultSpan[0];
2625 solution.dragViewSpanY = resultSpan[1];
2626 solution.isSolution = true;
2627 } else {
2628 solution.isSolution = false;
2629 }
2630 return solution;
2631 }
2632
2633 public void prepareChildForDrag(View child) {
2634 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002635 }
2636
Adam Cohen19f37922012-03-21 11:59:11 -07002637 /* This seems like it should be obvious and straight-forward, but when the direction vector
2638 needs to match with the notion of the dragView pushing other views, we have to employ
2639 a slightly more subtle notion of the direction vector. The question is what two points is
2640 the vector between? The center of the dragView and its desired destination? Not quite, as
2641 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2642 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2643 or right, which helps make pushing feel right.
2644 */
2645 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2646 int spanY, View dragView, int[] resultDirection) {
2647 int[] targetDestination = new int[2];
2648
2649 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2650 Rect dragRect = new Rect();
2651 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2652 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2653
2654 Rect dropRegionRect = new Rect();
2655 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2656 dragView, dropRegionRect, mIntersectingViews);
2657
2658 int dropRegionSpanX = dropRegionRect.width();
2659 int dropRegionSpanY = dropRegionRect.height();
2660
2661 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2662 dropRegionRect.height(), dropRegionRect);
2663
2664 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2665 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2666
2667 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2668 deltaX = 0;
2669 }
2670 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2671 deltaY = 0;
2672 }
2673
2674 if (deltaX == 0 && deltaY == 0) {
2675 // No idea what to do, give a random direction.
2676 resultDirection[0] = 1;
2677 resultDirection[1] = 0;
2678 } else {
2679 computeDirectionVector(deltaX, deltaY, resultDirection);
2680 }
2681 }
2682
2683 // For a given cell and span, fetch the set of views intersecting the region.
2684 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2685 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2686 if (boundingRect != null) {
2687 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2688 }
2689 intersectingViews.clear();
2690 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2691 Rect r1 = new Rect();
2692 final int count = mShortcutsAndWidgets.getChildCount();
2693 for (int i = 0; i < count; i++) {
2694 View child = mShortcutsAndWidgets.getChildAt(i);
2695 if (child == dragView) continue;
2696 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2697 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2698 if (Rect.intersects(r0, r1)) {
2699 mIntersectingViews.add(child);
2700 if (boundingRect != null) {
2701 boundingRect.union(r1);
2702 }
2703 }
2704 }
2705 }
2706
2707 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2708 View dragView, int[] result) {
2709 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2710 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2711 mIntersectingViews);
2712 return !mIntersectingViews.isEmpty();
2713 }
2714
2715 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002716 completeAndClearReorderPreviewAnimations();
2717 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2718 final int count = mShortcutsAndWidgets.getChildCount();
2719 for (int i = 0; i < count; i++) {
2720 View child = mShortcutsAndWidgets.getChildAt(i);
2721 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2722 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2723 lp.tmpCellX = lp.cellX;
2724 lp.tmpCellY = lp.cellY;
2725 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2726 0, false, false);
2727 }
Adam Cohen19f37922012-03-21 11:59:11 -07002728 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002729 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002730 }
Adam Cohen19f37922012-03-21 11:59:11 -07002731 }
2732
Adam Cohenbebf0422012-04-11 18:06:28 -07002733 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2734 View dragView, int[] direction, boolean commit) {
2735 int[] pixelXY = new int[2];
2736 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2737
2738 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002739 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002740 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2741
2742 setUseTempCoords(true);
2743 if (swapSolution != null && swapSolution.isSolution) {
2744 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2745 // committing anything or animating anything as we just want to determine if a solution
2746 // exists
2747 copySolutionToTempState(swapSolution, dragView);
2748 setItemPlacementDirty(true);
2749 animateItemsToSolution(swapSolution, dragView, commit);
2750
2751 if (commit) {
2752 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002753 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002754 setItemPlacementDirty(false);
2755 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002756 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2757 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002758 }
2759 mShortcutsAndWidgets.requestLayout();
2760 }
2761 return swapSolution.isSolution;
2762 }
2763
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002764 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002765 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002766 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002767 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002768
2769 if (resultSpan == null) {
2770 resultSpan = new int[2];
2771 }
2772
Adam Cohen19f37922012-03-21 11:59:11 -07002773 // When we are checking drop validity or actually dropping, we don't recompute the
2774 // direction vector, since we want the solution to match the preview, and it's possible
2775 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002776 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2777 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002778 mDirectionVector[0] = mPreviousReorderDirection[0];
2779 mDirectionVector[1] = mPreviousReorderDirection[1];
2780 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002781 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2782 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2783 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002784 }
2785 } else {
2786 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2787 mPreviousReorderDirection[0] = mDirectionVector[0];
2788 mPreviousReorderDirection[1] = mDirectionVector[1];
2789 }
2790
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002791 // Find a solution involving pushing / displacing any items in the way
2792 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002793 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2794
2795 // We attempt the approach which doesn't shuffle views at all
2796 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2797 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2798
2799 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002800
2801 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2802 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002803 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2804 finalSolution = swapSolution;
2805 } else if (noShuffleSolution.isSolution) {
2806 finalSolution = noShuffleSolution;
2807 }
2808
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002809 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002810 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002811 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2812 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002813 result[0] = finalSolution.dragViewX;
2814 result[1] = finalSolution.dragViewY;
2815 resultSpan[0] = finalSolution.dragViewSpanX;
2816 resultSpan[1] = finalSolution.dragViewSpanY;
2817 } else {
2818 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2819 }
2820 return result;
2821 }
2822
Adam Cohen482ed822012-03-02 14:15:13 -08002823 boolean foundSolution = true;
2824 if (!DESTRUCTIVE_REORDER) {
2825 setUseTempCoords(true);
2826 }
2827
2828 if (finalSolution != null) {
2829 result[0] = finalSolution.dragViewX;
2830 result[1] = finalSolution.dragViewY;
2831 resultSpan[0] = finalSolution.dragViewSpanX;
2832 resultSpan[1] = finalSolution.dragViewSpanY;
2833
2834 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2835 // committing anything or animating anything as we just want to determine if a solution
2836 // exists
2837 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2838 if (!DESTRUCTIVE_REORDER) {
2839 copySolutionToTempState(finalSolution, dragView);
2840 }
2841 setItemPlacementDirty(true);
2842 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2843
Adam Cohen19f37922012-03-21 11:59:11 -07002844 if (!DESTRUCTIVE_REORDER &&
2845 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002846 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002847 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002848 setItemPlacementDirty(false);
2849 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002850 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2851 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002852 }
2853 }
2854 } else {
2855 foundSolution = false;
2856 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2857 }
2858
2859 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2860 setUseTempCoords(false);
2861 }
Adam Cohen482ed822012-03-02 14:15:13 -08002862
Michael Jurkaa52570f2012-03-20 03:18:20 -07002863 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002864 return result;
2865 }
2866
Adam Cohen19f37922012-03-21 11:59:11 -07002867 void setItemPlacementDirty(boolean dirty) {
2868 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002869 }
Adam Cohen19f37922012-03-21 11:59:11 -07002870 boolean isItemPlacementDirty() {
2871 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002872 }
2873
2874 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002875 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002876 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2877 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002878 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002879 boolean isSolution = false;
2880 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2881
Adam Cohenf3900c22012-11-16 18:28:11 -08002882 void save() {
2883 // Copy current state into savedMap
2884 for (View v: map.keySet()) {
2885 map.get(v).copy(savedMap.get(v));
2886 }
2887 }
2888
2889 void restore() {
2890 // Restore current state from savedMap
2891 for (View v: savedMap.keySet()) {
2892 savedMap.get(v).copy(map.get(v));
2893 }
2894 }
2895
2896 void add(View v, CellAndSpan cs) {
2897 map.put(v, cs);
2898 savedMap.put(v, new CellAndSpan());
2899 sortedViews.add(v);
2900 }
2901
Adam Cohen482ed822012-03-02 14:15:13 -08002902 int area() {
2903 return dragViewSpanX * dragViewSpanY;
2904 }
Adam Cohen8baab352012-03-20 17:39:21 -07002905 }
2906
2907 private class CellAndSpan {
2908 int x, y;
2909 int spanX, spanY;
2910
Adam Cohenf3900c22012-11-16 18:28:11 -08002911 public CellAndSpan() {
2912 }
2913
2914 public void copy(CellAndSpan copy) {
2915 copy.x = x;
2916 copy.y = y;
2917 copy.spanX = spanX;
2918 copy.spanY = spanY;
2919 }
2920
Adam Cohen8baab352012-03-20 17:39:21 -07002921 public CellAndSpan(int x, int y, int spanX, int spanY) {
2922 this.x = x;
2923 this.y = y;
2924 this.spanX = spanX;
2925 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002926 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002927
2928 public String toString() {
2929 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2930 }
2931
Adam Cohen482ed822012-03-02 14:15:13 -08002932 }
2933
Adam Cohendf035382011-04-11 17:22:04 -07002934 /**
2935 * Find a vacant area that will fit the given bounds nearest the requested
2936 * cell location. Uses Euclidean distance to score multiple vacant areas.
2937 *
2938 * @param pixelX The X location at which you want to search for a vacant area.
2939 * @param pixelY The Y location at which you want to search for a vacant area.
2940 * @param spanX Horizontal span of the object.
2941 * @param spanY Vertical span of the object.
2942 * @param ignoreView Considers space occupied by this view as unoccupied
2943 * @param result Previously returned value to possibly recycle.
2944 * @return The X, Y cell of a vacant area that can contain this object,
2945 * nearest the requested location.
2946 */
2947 int[] findNearestVacantArea(
2948 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2949 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2950 }
2951
2952 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002953 * Find a vacant area that will fit the given bounds nearest the requested
2954 * cell location. Uses Euclidean distance to score multiple vacant areas.
2955 *
2956 * @param pixelX The X location at which you want to search for a vacant area.
2957 * @param pixelY The Y location at which you want to search for a vacant area.
2958 * @param minSpanX The minimum horizontal span required
2959 * @param minSpanY The minimum vertical span required
2960 * @param spanX Horizontal span of the object.
2961 * @param spanY Vertical span of the object.
2962 * @param ignoreView Considers space occupied by this view as unoccupied
2963 * @param result Previously returned value to possibly recycle.
2964 * @return The X, Y cell of a vacant area that can contain this object,
2965 * nearest the requested location.
2966 */
2967 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2968 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002969 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2970 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002971 }
2972
2973 /**
Adam Cohendf035382011-04-11 17:22:04 -07002974 * Find a starting cell position that will fit the given bounds nearest the requested
2975 * cell location. Uses Euclidean distance to score multiple vacant areas.
2976 *
2977 * @param pixelX The X location at which you want to search for a vacant area.
2978 * @param pixelY The Y location at which you want to search for a vacant area.
2979 * @param spanX Horizontal span of the object.
2980 * @param spanY Vertical span of the object.
2981 * @param ignoreView Considers space occupied by this view as unoccupied
2982 * @param result Previously returned value to possibly recycle.
2983 * @return The X, Y cell of a vacant area that can contain this object,
2984 * nearest the requested location.
2985 */
2986 int[] findNearestArea(
2987 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2988 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2989 }
2990
Michael Jurka0280c3b2010-09-17 15:00:07 -07002991 boolean existsEmptyCell() {
2992 return findCellForSpan(null, 1, 1);
2993 }
2994
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002995 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002996 * Finds the upper-left coordinate of the first rectangle in the grid that can
2997 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2998 * then this method will only return coordinates for rectangles that contain the cell
2999 * (intersectX, intersectY)
3000 *
3001 * @param cellXY The array that will contain the position of a vacant cell if such a cell
3002 * can be found.
3003 * @param spanX The horizontal span of the cell we want to find.
3004 * @param spanY The vertical span of the cell we want to find.
3005 *
3006 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07003007 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07003008 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08003009 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003010 }
3011
3012 /**
3013 * Like above, but ignores any cells occupied by the item "ignoreView"
3014 *
3015 * @param cellXY The array that will contain the position of a vacant cell if such a cell
3016 * can be found.
3017 * @param spanX The horizontal span of the cell we want to find.
3018 * @param spanY The vertical span of the cell we want to find.
3019 * @param ignoreView The home screen item we should treat as not occupying any space
3020 * @return
3021 */
3022 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08003023 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
3024 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003025 }
3026
3027 /**
3028 * Like above, but if intersectX and intersectY are not -1, then this method will try to
3029 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
3030 *
3031 * @param spanX The horizontal span of the cell we want to find.
3032 * @param spanY The vertical span of the cell we want to find.
3033 * @param ignoreView The home screen item we should treat as not occupying any space
3034 * @param intersectX The X coordinate of the cell that we should try to overlap
3035 * @param intersectX The Y coordinate of the cell that we should try to overlap
3036 *
3037 * @return True if a vacant cell of the specified dimension was found, false otherwise.
3038 */
3039 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
3040 int intersectX, int intersectY) {
3041 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08003042 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003043 }
3044
3045 /**
3046 * The superset of the above two methods
3047 */
3048 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08003049 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07003050 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08003051 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003052
Michael Jurka28750fb2010-09-24 17:43:49 -07003053 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003054 while (true) {
3055 int startX = 0;
3056 if (intersectX >= 0) {
3057 startX = Math.max(startX, intersectX - (spanX - 1));
3058 }
3059 int endX = mCountX - (spanX - 1);
3060 if (intersectX >= 0) {
3061 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
3062 }
3063 int startY = 0;
3064 if (intersectY >= 0) {
3065 startY = Math.max(startY, intersectY - (spanY - 1));
3066 }
3067 int endY = mCountY - (spanY - 1);
3068 if (intersectY >= 0) {
3069 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
3070 }
3071
Winson Chungbbc60d82010-11-11 16:34:41 -08003072 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003073 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08003074 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003075 for (int i = 0; i < spanX; i++) {
3076 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08003077 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08003078 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07003079 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08003080 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003081 continue inner;
3082 }
3083 }
3084 }
3085 if (cellXY != null) {
3086 cellXY[0] = x;
3087 cellXY[1] = y;
3088 }
Michael Jurka28750fb2010-09-24 17:43:49 -07003089 foundCell = true;
3090 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003091 }
3092 }
3093 if (intersectX == -1 && intersectY == -1) {
3094 break;
3095 } else {
3096 // if we failed to find anything, try again but without any requirements of
3097 // intersecting
3098 intersectX = -1;
3099 intersectY = -1;
3100 continue;
3101 }
3102 }
3103
Michael Jurkac6ee42e2010-09-30 12:04:50 -07003104 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08003105 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07003106 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003107 }
3108
3109 /**
Winson Chungc07918d2011-07-01 15:35:26 -07003110 * A drag event has begun over this layout.
3111 * It may have begun over this layout (in which case onDragChild is called first),
3112 * or it may have begun on another layout.
3113 */
3114 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07003115 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07003116 mDragging = true;
3117 }
3118
3119 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07003120 * Called when drag has left this CellLayout or has been completed (successfully or not)
3121 */
3122 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07003123 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07003124 // This can actually be called when we aren't in a drag, e.g. when adding a new
3125 // item to this layout via the customize drawer.
3126 // Guard against that case.
3127 if (mDragging) {
3128 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07003129 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07003130
3131 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08003132 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07003133 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
3134 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07003135 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08003136 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07003137 }
3138
3139 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07003140 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07003141 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07003142 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003143 *
3144 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003145 */
Adam Cohen716b51e2011-06-30 12:09:54 -07003146 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07003147 if (child != null) {
3148 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08003149 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07003150 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07003151 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003152 }
3153
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003154 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003155 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07003156 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003157 * @param cellX X coordinate of upper left corner expressed as a cell position
3158 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07003159 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003160 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07003161 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003162 */
Adam Cohend41fbf52012-02-16 23:53:59 -08003163 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003164 final int cellWidth = mCellWidth;
3165 final int cellHeight = mCellHeight;
3166 final int widthGap = mWidthGap;
3167 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07003168
Winson Chung4b825dcd2011-06-19 12:41:22 -07003169 final int hStartPadding = getPaddingLeft();
3170 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07003171
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003172 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
3173 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
3174
3175 int x = hStartPadding + cellX * (cellWidth + widthGap);
3176 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07003177
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07003178 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003179 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003180
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003181 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07003182 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003183 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07003184 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003185 * @param width Width in pixels
3186 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07003187 * @param result An array of length 2 in which to store the result (may be null).
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003188 */
Winson Chung66700732013-08-20 16:56:15 -07003189 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07003190 LauncherAppState app = LauncherAppState.getInstance();
3191 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07003192 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
3193 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07003194
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003195 // Always assume we're working with the smallest span to make sure we
3196 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07003197 int parentWidth = grid.calculateCellWidth(grid.widthPx
3198 - padding.left - padding.right, (int) grid.numColumns);
3199 int parentHeight = grid.calculateCellHeight(grid.heightPx
3200 - padding.top - padding.bottom, (int) grid.numRows);
3201 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04003202
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003203 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07003204 int spanX = (int) Math.ceil(width / (float) smallerSize);
3205 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04003206
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07003207 if (result == null) {
3208 return new int[] { spanX, spanY };
3209 }
3210 result[0] = spanX;
3211 result[1] = spanY;
3212 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003213 }
3214
Michael Jurkaf12c75c2011-01-25 22:41:40 -08003215 public int[] cellSpansToSize(int hSpans, int vSpans) {
3216 int[] size = new int[2];
3217 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
3218 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
3219 return size;
3220 }
3221
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003222 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08003223 * Calculate the grid spans needed to fit given item
3224 */
3225 public void calculateSpans(ItemInfo info) {
3226 final int minWidth;
3227 final int minHeight;
3228
3229 if (info instanceof LauncherAppWidgetInfo) {
3230 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
3231 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
3232 } else if (info instanceof PendingAddWidgetInfo) {
3233 minWidth = ((PendingAddWidgetInfo) info).minWidth;
3234 minHeight = ((PendingAddWidgetInfo) info).minHeight;
3235 } else {
3236 // It's not a widget, so it must be 1x1
3237 info.spanX = info.spanY = 1;
3238 return;
3239 }
3240 int[] spans = rectToCell(minWidth, minHeight, null);
3241 info.spanX = spans[0];
3242 info.spanY = spans[1];
3243 }
3244
3245 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003246 * Find the first vacant cell, if there is one.
3247 *
3248 * @param vacant Holds the x and y coordinate of the vacant cell
3249 * @param spanX Horizontal cell span.
3250 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07003251 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003252 * @return True if a vacant cell was found
3253 */
3254 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003255
Michael Jurka0280c3b2010-09-17 15:00:07 -07003256 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003257 }
3258
3259 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
3260 int xCount, int yCount, boolean[][] occupied) {
3261
Sunny Goyal71b5c0b2015-01-08 16:59:04 -08003262 for (int y = 0; (y + spanY) <= yCount; y++) {
3263 for (int x = 0; (x + spanX) <= xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003264 boolean available = !occupied[x][y];
Sunny Goyal71b5c0b2015-01-08 16:59:04 -08003265out: for (int i = x; i < x + spanX; i++) {
3266 for (int j = y; j < y + spanY; j++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003267 available = available && !occupied[i][j];
3268 if (!available) break out;
3269 }
3270 }
3271
3272 if (available) {
3273 vacant[0] = x;
3274 vacant[1] = y;
3275 return true;
3276 }
3277 }
3278 }
3279
3280 return false;
3281 }
3282
Michael Jurka0280c3b2010-09-17 15:00:07 -07003283 private void clearOccupiedCells() {
3284 for (int x = 0; x < mCountX; x++) {
3285 for (int y = 0; y < mCountY; y++) {
3286 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003287 }
3288 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07003289 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003290
Adam Cohend41fbf52012-02-16 23:53:59 -08003291 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003292 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08003293 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003294 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003295
Adam Cohend4844c32011-02-18 19:25:06 -08003296 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003297 markCellsAsOccupiedForView(view, mOccupied);
3298 }
3299 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003300 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003301 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003302 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003303 }
3304
Adam Cohend4844c32011-02-18 19:25:06 -08003305 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003306 markCellsAsUnoccupiedForView(view, mOccupied);
3307 }
3308 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003309 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003310 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003311 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003312 }
3313
Adam Cohen482ed822012-03-02 14:15:13 -08003314 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
3315 boolean value) {
3316 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003317 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
3318 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08003319 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003320 }
3321 }
3322 }
3323
Adam Cohen2801caf2011-05-13 20:57:39 -07003324 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003325 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003326 (Math.max((mCountX - 1), 0) * mWidthGap);
3327 }
3328
3329 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003330 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003331 (Math.max((mCountY - 1), 0) * mHeightGap);
3332 }
3333
Michael Jurka66d72172011-04-12 16:29:25 -07003334 public boolean isOccupied(int x, int y) {
3335 if (x < mCountX && y < mCountY) {
3336 return mOccupied[x][y];
3337 } else {
3338 throw new RuntimeException("Position exceeds the bound of this CellLayout");
3339 }
3340 }
3341
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003342 @Override
3343 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3344 return new CellLayout.LayoutParams(getContext(), attrs);
3345 }
3346
3347 @Override
3348 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3349 return p instanceof CellLayout.LayoutParams;
3350 }
3351
3352 @Override
3353 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3354 return new CellLayout.LayoutParams(p);
3355 }
3356
Winson Chungaafa03c2010-06-11 17:34:16 -07003357 public static class CellLayoutAnimationController extends LayoutAnimationController {
3358 public CellLayoutAnimationController(Animation animation, float delay) {
3359 super(animation, delay);
3360 }
3361
3362 @Override
3363 protected long getDelayForView(View view) {
3364 return (int) (Math.random() * 150);
3365 }
3366 }
3367
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003368 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
3369 /**
3370 * Horizontal location of the item in the grid.
3371 */
3372 @ViewDebug.ExportedProperty
3373 public int cellX;
3374
3375 /**
3376 * Vertical location of the item in the grid.
3377 */
3378 @ViewDebug.ExportedProperty
3379 public int cellY;
3380
3381 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003382 * Temporary horizontal location of the item in the grid during reorder
3383 */
3384 public int tmpCellX;
3385
3386 /**
3387 * Temporary vertical location of the item in the grid during reorder
3388 */
3389 public int tmpCellY;
3390
3391 /**
3392 * Indicates that the temporary coordinates should be used to layout the items
3393 */
3394 public boolean useTmpCoords;
3395
3396 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003397 * Number of cells spanned horizontally by the item.
3398 */
3399 @ViewDebug.ExportedProperty
3400 public int cellHSpan;
3401
3402 /**
3403 * Number of cells spanned vertically by the item.
3404 */
3405 @ViewDebug.ExportedProperty
3406 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07003407
Adam Cohen1b607ed2011-03-03 17:26:50 -08003408 /**
3409 * Indicates whether the item will set its x, y, width and height parameters freely,
3410 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
3411 */
Adam Cohend4844c32011-02-18 19:25:06 -08003412 public boolean isLockedToGrid = true;
3413
Adam Cohen482ed822012-03-02 14:15:13 -08003414 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07003415 * Indicates that this item should use the full extents of its parent.
3416 */
3417 public boolean isFullscreen = false;
3418
3419 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003420 * Indicates whether this item can be reordered. Always true except in the case of the
3421 * the AllApps button.
3422 */
3423 public boolean canReorder = true;
3424
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003425 // X coordinate of the view in the layout.
3426 @ViewDebug.ExportedProperty
3427 int x;
3428 // Y coordinate of the view in the layout.
3429 @ViewDebug.ExportedProperty
3430 int y;
3431
Romain Guy84f296c2009-11-04 15:00:44 -08003432 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07003433
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003434 public LayoutParams(Context c, AttributeSet attrs) {
3435 super(c, attrs);
3436 cellHSpan = 1;
3437 cellVSpan = 1;
3438 }
3439
3440 public LayoutParams(ViewGroup.LayoutParams source) {
3441 super(source);
3442 cellHSpan = 1;
3443 cellVSpan = 1;
3444 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003445
3446 public LayoutParams(LayoutParams source) {
3447 super(source);
3448 this.cellX = source.cellX;
3449 this.cellY = source.cellY;
3450 this.cellHSpan = source.cellHSpan;
3451 this.cellVSpan = source.cellVSpan;
3452 }
3453
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003454 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08003455 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003456 this.cellX = cellX;
3457 this.cellY = cellY;
3458 this.cellHSpan = cellHSpan;
3459 this.cellVSpan = cellVSpan;
3460 }
3461
Adam Cohen2374abf2013-04-16 14:56:57 -07003462 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
3463 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08003464 if (isLockedToGrid) {
3465 final int myCellHSpan = cellHSpan;
3466 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07003467 int myCellX = useTmpCoords ? tmpCellX : cellX;
3468 int myCellY = useTmpCoords ? tmpCellY : cellY;
3469
3470 if (invertHorizontally) {
3471 myCellX = colCount - myCellX - cellHSpan;
3472 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08003473
Adam Cohend4844c32011-02-18 19:25:06 -08003474 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3475 leftMargin - rightMargin;
3476 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3477 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08003478 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3479 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08003480 }
3481 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003482
Winson Chungaafa03c2010-06-11 17:34:16 -07003483 public String toString() {
3484 return "(" + this.cellX + ", " + this.cellY + ")";
3485 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003486
3487 public void setWidth(int width) {
3488 this.width = width;
3489 }
3490
3491 public int getWidth() {
3492 return width;
3493 }
3494
3495 public void setHeight(int height) {
3496 this.height = height;
3497 }
3498
3499 public int getHeight() {
3500 return height;
3501 }
3502
3503 public void setX(int x) {
3504 this.x = x;
3505 }
3506
3507 public int getX() {
3508 return x;
3509 }
3510
3511 public void setY(int y) {
3512 this.y = y;
3513 }
3514
3515 public int getY() {
3516 return y;
3517 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003518 }
3519
Michael Jurka0280c3b2010-09-17 15:00:07 -07003520 // This class stores info for two purposes:
3521 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3522 // its spanX, spanY, and the screen it is on
3523 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3524 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3525 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003526 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003527 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003528 int cellX = -1;
3529 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003530 int spanX;
3531 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003532 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003533 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003534
Adam Cohene0aaa0d2014-05-12 12:44:22 -07003535 CellInfo(View v, ItemInfo info) {
3536 cell = v;
3537 cellX = info.cellX;
3538 cellY = info.cellY;
3539 spanX = info.spanX;
3540 spanY = info.spanY;
3541 screenId = info.screenId;
3542 container = info.container;
3543 }
3544
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003545 @Override
3546 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003547 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3548 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003549 }
3550 }
Michael Jurkad771c962011-08-09 15:00:48 -07003551
3552 public boolean lastDownOnOccupiedCell() {
3553 return mLastDownOnOccupiedCell;
3554 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003555}