blob: a348008d84b940309f704679780c355ddaff2b1a [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;
Adam Cohen1462de32012-07-24 22:34:36 -070038import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080039import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070041import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070042import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080047import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070048import android.view.animation.DecelerateInterpolator;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049
Daniel Sandler325dc232013-06-05 22:57:57 -040050import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyale9b651e2015-04-24 11:44:51 -070051import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
52import com.android.launcher3.accessibility.FolderAccessibilityHelper;
53import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Adam Cohen091440a2015-03-18 14:16:05 -070054import com.android.launcher3.util.Thunk;
Hyunyoung Song3f471442015-04-08 19:01:34 -070055import com.android.launcher3.widget.PendingAddWidgetInfo;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070056
Adam Cohen69ce2e52011-07-03 19:25:21 -070057import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070058import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080059import java.util.Collections;
60import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070061import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080062import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070063
Michael Jurkabdb5c532011-02-01 15:05:06 -080064public class CellLayout extends ViewGroup {
Sunny Goyale9b651e2015-04-24 11:44:51 -070065 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
66 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
67
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;
Adam Cohen091440a2015-03-18 14:16:05 -070071 @Thunk int mCellWidth;
72 @Thunk 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 Cohen091440a2015-03-18 14:16:05 -070076 @Thunk int mCountX;
77 @Thunk 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;
Adam Cohen091440a2015-03-18 14:16:05 -070081 @Thunk int mWidthGap;
82 @Thunk 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];
Adam Cohen091440a2015-03-18 14:16:05 -070090 @Thunk 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;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700123
Winson Chung150fbab2010-09-29 17:14:26 -0700124 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700125 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700126 @Thunk Rect[] mDragOutlines = new Rect[4];
127 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700128 private InterruptibleInOutAnimator[] mDragOutlineAnims =
129 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700130
131 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700132 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700133 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700134
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700135 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800136
Adam Cohen091440a2015-03-18 14:16:05 -0700137 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
Adam Cohen482ed822012-03-02 14:15:13 -0800138 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800139 private HashMap<View, ReorderPreviewAnimation>
140 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700141
142 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700143
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700144 // When a drag operation is in progress, holds the nearest cell to the touch point
145 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800146
Joe Onorato4be866d2010-10-10 11:26:02 -0700147 private boolean mDragging = false;
148
Patrick Dubroyce34a972010-10-19 10:34:32 -0700149 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700150 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700151
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800152 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700153 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800154
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800155 public static final int MODE_SHOW_REORDER_HINT = 0;
156 public static final int MODE_DRAG_OVER = 1;
157 public static final int MODE_ON_DROP = 2;
158 public static final int MODE_ON_DROP_EXTERNAL = 3;
159 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700160 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800161 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
162
Adam Cohena897f392012-04-27 18:12:05 -0700163 static final int LANDSCAPE = 0;
164 static final int PORTRAIT = 1;
165
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800166 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700167 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700168 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700169
Adam Cohen482ed822012-03-02 14:15:13 -0800170 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
171 private Rect mOccupiedRect = new Rect();
172 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700173 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700174 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700175 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800176
Winson Chung3a6e7f32013-10-09 15:50:52 -0700177 private Rect mTempRect = new Rect();
178
Michael Jurkaca993832012-06-29 15:17:04 -0700179 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700180
Adam Cohenc9735cf2015-01-23 16:11:55 -0800181 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700182 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800183 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800184
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800185 public CellLayout(Context context) {
186 this(context, null);
187 }
188
189 public CellLayout(Context context, AttributeSet attrs) {
190 this(context, attrs, 0);
191 }
192
193 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
194 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700195 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700196
197 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
198 // the user where a dragged item will land when dropped.
199 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800200 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700201 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700202
Winson Chung892c74d2013-08-22 16:15:50 -0700203 LauncherAppState app = LauncherAppState.getInstance();
204 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
206
Winson Chung11a1a532013-09-13 11:14:45 -0700207 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800208 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700209 mWidthGap = mOriginalWidthGap = 0;
210 mHeightGap = mOriginalHeightGap = 0;
211 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700212 mCountX = (int) grid.numColumns;
213 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700214 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800215 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700216 mPreviousReorderDirection[0] = INVALID_DIRECTION;
217 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800218
219 a.recycle();
220
221 setAlwaysDrawnWithCacheEnabled(false);
222
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700223 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700224 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700225
Adam Cohen410f3cd2013-09-22 12:09:32 -0700226 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
227 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800228
Adam Cohenb5ba0972011-09-07 18:02:31 -0700229 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
230 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
231 mForegroundPadding =
232 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800233
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800234 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700235 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700236
Winson Chungb26f3d62011-06-02 10:49:29 -0700237 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700238 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700239
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700240 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700241 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700242 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700243 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800244 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700245 }
246
247 // When dragging things around the home screens, we show a green outline of
248 // where the item will land. The outlines gradually fade out, leaving a trail
249 // behind the drag path.
250 // Set up all the animations that are used to implement this fading.
251 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700252 final float fromAlphaValue = 0;
253 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700254
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700255 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700256
257 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700258 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100259 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700260 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700261 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700262 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700263 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700264 final Bitmap outline = (Bitmap)anim.getTag();
265
266 // If an animation is started and then stopped very quickly, we can still
267 // get spurious updates we've cleared the tag. Guard against this.
268 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700269 @SuppressWarnings("all") // suppress dead code warning
270 final boolean debug = false;
271 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700272 Object val = animation.getAnimatedValue();
273 Log.d(TAG, "anim " + thisIndex + " update: " + val +
274 ", isStopped " + anim.isStopped());
275 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700276 // Try to prevent it from continuing to run
277 animation.cancel();
278 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700279 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800280 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700282 }
283 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700284 // The animation holds a reference to the drag outline bitmap as long is it's
285 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700286 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700287 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700288 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700289 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700290 anim.setTag(null);
291 }
292 }
293 });
294 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700295 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700296
Michael Jurka18014792010-10-14 09:01:34 -0700297 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700298 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800299
Michael Jurkaa52570f2012-03-20 03:18:20 -0700300 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700301 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700302 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700303
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700304 mTouchFeedbackView = new ClickShadowView(context);
305 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700306 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700307 }
308
Adam Cohenc9735cf2015-01-23 16:11:55 -0800309 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700310 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800311 mUseTouchHelper = enable;
312 if (!enable) {
313 ViewCompat.setAccessibilityDelegate(this, null);
314 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
315 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
316 setOnClickListener(mLauncher);
317 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700318 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
319 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
320 mTouchHelper = new WorkspaceAccessibilityHelper(this);
321 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
322 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
323 mTouchHelper = new FolderAccessibilityHelper(this);
324 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800325 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
326 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
327 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
328 setOnClickListener(mTouchHelper);
329 }
330
331 // Invalidate the accessibility hierarchy
332 if (getParent() != null) {
333 getParent().notifySubtreeAccessibilityStateChanged(
334 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
335 }
336 }
337
338 @Override
339 public boolean dispatchHoverEvent(MotionEvent event) {
340 // Always attempt to dispatch hover events to accessibility first.
341 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
342 return true;
343 }
344 return super.dispatchHoverEvent(event);
345 }
346
347 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800348 public boolean onInterceptTouchEvent(MotionEvent ev) {
349 if (mUseTouchHelper ||
350 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
351 return true;
352 }
353 return false;
354 }
355
Chris Craik01f2d7f2013-10-01 14:41:56 -0700356 public void enableHardwareLayer(boolean hasLayer) {
357 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700358 }
359
360 public void buildHardwareLayer() {
361 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700362 }
363
Adam Cohen307fe232012-08-16 17:55:58 -0700364 public float getChildrenScale() {
365 return mIsHotseat ? mHotseatScale : 1.0f;
366 }
367
Winson Chung5f8afe62013-08-12 16:19:28 -0700368 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700369 mFixedCellWidth = mCellWidth = width;
370 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700371 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
372 mCountX, mCountY);
373 }
374
Adam Cohen2801caf2011-05-13 20:57:39 -0700375 public void setGridSize(int x, int y) {
376 mCountX = x;
377 mCountY = y;
378 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800379 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700380 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700381 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700382 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700383 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700384 }
385
Adam Cohen2374abf2013-04-16 14:56:57 -0700386 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
387 public void setInvertIfRtl(boolean invert) {
388 mShortcutsAndWidgets.setInvertIfRtl(invert);
389 }
390
Adam Cohen917e3882013-10-31 15:03:35 -0700391 public void setDropPending(boolean pending) {
392 mDropPending = pending;
393 }
394
395 public boolean isDropPending() {
396 return mDropPending;
397 }
398
Adam Cohenb5ba0972011-09-07 18:02:31 -0700399 void setOverScrollAmount(float r, boolean left) {
400 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
401 mOverScrollForegroundDrawable = mOverScrollLeft;
402 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
403 mOverScrollForegroundDrawable = mOverScrollRight;
404 }
405
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700406 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700407 mForegroundAlpha = (int) Math.round((r * 255));
408 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
409 invalidate();
410 }
411
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700412 void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700413 if (icon == null || background == null) {
414 mTouchFeedbackView.setBitmap(null);
415 mTouchFeedbackView.animate().cancel();
416 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700417 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700418 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
419 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700420 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800421 }
422 }
423
Winson Chung59a488a2013-12-10 12:32:14 -0800424 void disableBackground() {
425 mDrawBackground = false;
426 }
427
Adam Cohenc50438c2014-08-19 17:43:05 -0700428 void disableDragTarget() {
429 mIsDragTarget = false;
430 }
431
432 boolean isDragTarget() {
433 return mIsDragTarget;
434 }
435
436 void setIsDragOverlapping(boolean isDragOverlapping) {
437 if (mIsDragOverlapping != isDragOverlapping) {
438 mIsDragOverlapping = isDragOverlapping;
Adam Cohenc50438c2014-08-19 17:43:05 -0700439 invalidate();
440 }
441 }
442
Michael Jurka33945b22010-12-21 18:19:38 -0800443 boolean getIsDragOverlapping() {
444 return mIsDragOverlapping;
445 }
446
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700447 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700448 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700449 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
450 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
451 // When we're small, we are either drawn normally or in the "accepts drops" state (during
452 // a drag). However, we also drag the mini hover background *over* one of those two
453 // backgrounds
Winson Chung59a488a2013-12-10 12:32:14 -0800454 if (mDrawBackground && mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700455 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800456
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700457 if (mIsDragOverlapping) {
Michael Jurka33945b22010-12-21 18:19:38 -0800458 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700459 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700460 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700461 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700462 }
Michael Jurka33945b22010-12-21 18:19:38 -0800463
464 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
465 bg.setBounds(mBackgroundRect);
466 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700467 }
Romain Guya6abce82009-11-10 02:54:41 -0800468
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700469 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700470 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700471 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700472 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800473 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700474 mTempRect.set(r);
475 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700476 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700477 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700478 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700479 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700480 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800481
Adam Cohen482ed822012-03-02 14:15:13 -0800482 if (DEBUG_VISUALIZE_OCCUPIED) {
483 int[] pt = new int[2];
484 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700485 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800486 for (int i = 0; i < mCountX; i++) {
487 for (int j = 0; j < mCountY; j++) {
488 if (mOccupied[i][j]) {
489 cellToPoint(i, j, pt);
490 canvas.save();
491 canvas.translate(pt[0], pt[1]);
492 cd.draw(canvas);
493 canvas.restore();
494 }
495 }
496 }
497 }
498
Andrew Flynn850d2e72012-04-26 16:51:20 -0700499 int previewOffset = FolderRingAnimator.sPreviewSize;
500
Adam Cohen69ce2e52011-07-03 19:25:21 -0700501 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700502 LauncherAppState app = LauncherAppState.getInstance();
503 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700504 for (int i = 0; i < mFolderOuterRings.size(); i++) {
505 FolderRingAnimator fra = mFolderOuterRings.get(i);
506
Adam Cohen5108bc02013-09-20 17:04:51 -0700507 Drawable d;
508 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700509 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700510 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700511
Winson Chung89f97052013-09-20 11:32:26 -0700512 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700513 int centerX = mTempLocation[0] + mCellWidth / 2;
514 int centerY = mTempLocation[1] + previewOffset / 2 +
515 child.getPaddingTop() + grid.folderBackgroundOffset;
516
Adam Cohen5108bc02013-09-20 17:04:51 -0700517 // Draw outer ring, if it exists
518 if (FolderIcon.HAS_OUTER_RING) {
519 d = FolderRingAnimator.sSharedOuterRingDrawable;
520 width = (int) (fra.getOuterRingSize() * getChildrenScale());
521 height = width;
522 canvas.save();
523 canvas.translate(centerX - width / 2, centerY - height / 2);
524 d.setBounds(0, 0, width, height);
525 d.draw(canvas);
526 canvas.restore();
527 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700528
Winson Chung89f97052013-09-20 11:32:26 -0700529 // Draw inner ring
530 d = FolderRingAnimator.sSharedInnerRingDrawable;
531 width = (int) (fra.getInnerRingSize() * getChildrenScale());
532 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700533 canvas.save();
534 canvas.translate(centerX - width / 2, centerY - width / 2);
535 d.setBounds(0, 0, width, height);
536 d.draw(canvas);
537 canvas.restore();
538 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700539 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700540
541 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
542 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
543 int width = d.getIntrinsicWidth();
544 int height = d.getIntrinsicHeight();
545
546 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700547 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700548 if (child != null) {
549 int centerX = mTempLocation[0] + mCellWidth / 2;
550 int centerY = mTempLocation[1] + previewOffset / 2 +
551 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700552
Winson Chung89f97052013-09-20 11:32:26 -0700553 canvas.save();
554 canvas.translate(centerX - width / 2, centerY - width / 2);
555 d.setBounds(0, 0, width, height);
556 d.draw(canvas);
557 canvas.restore();
558 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700559 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700560 }
561
Adam Cohenb5ba0972011-09-07 18:02:31 -0700562 @Override
563 protected void dispatchDraw(Canvas canvas) {
564 super.dispatchDraw(canvas);
565 if (mForegroundAlpha > 0) {
566 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700567 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700568 }
569 }
570
Adam Cohen69ce2e52011-07-03 19:25:21 -0700571 public void showFolderAccept(FolderRingAnimator fra) {
572 mFolderOuterRings.add(fra);
573 }
574
575 public void hideFolderAccept(FolderRingAnimator fra) {
576 if (mFolderOuterRings.contains(fra)) {
577 mFolderOuterRings.remove(fra);
578 }
579 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700580 }
581
Adam Cohenc51934b2011-07-26 21:07:43 -0700582 public void setFolderLeaveBehindCell(int x, int y) {
583 mFolderLeaveBehindCell[0] = x;
584 mFolderLeaveBehindCell[1] = y;
585 invalidate();
586 }
587
588 public void clearFolderLeaveBehind() {
589 mFolderLeaveBehindCell[0] = -1;
590 mFolderLeaveBehindCell[1] = -1;
591 invalidate();
592 }
593
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700594 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700595 public boolean shouldDelayChildPressedState() {
596 return false;
597 }
598
Adam Cohen1462de32012-07-24 22:34:36 -0700599 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700600 try {
601 dispatchRestoreInstanceState(states);
602 } catch (IllegalArgumentException ex) {
603 if (LauncherAppState.isDogfoodBuild()) {
604 throw ex;
605 }
606 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
607 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
608 }
Adam Cohen1462de32012-07-24 22:34:36 -0700609 }
610
Michael Jurkae6235dd2011-10-04 15:02:05 -0700611 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700612 public void cancelLongPress() {
613 super.cancelLongPress();
614
615 // Cancel long press for all children
616 final int count = getChildCount();
617 for (int i = 0; i < count; i++) {
618 final View child = getChildAt(i);
619 child.cancelLongPress();
620 }
621 }
622
Michael Jurkadee05892010-07-27 10:01:56 -0700623 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
624 mInterceptTouchListener = listener;
625 }
626
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800627 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700628 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800629 }
630
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800631 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700632 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800633 }
634
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800635 public void setIsHotseat(boolean isHotseat) {
636 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700637 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800638 }
639
Sunny Goyale9b651e2015-04-24 11:44:51 -0700640 public boolean isHotseat() {
641 return mIsHotseat;
642 }
643
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800644 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700645 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700646 final LayoutParams lp = params;
647
Andrew Flynnde38e422012-05-08 11:22:15 -0700648 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800649 if (child instanceof BubbleTextView) {
650 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700651 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800652 }
653
Adam Cohen307fe232012-08-16 17:55:58 -0700654 child.setScaleX(getChildrenScale());
655 child.setScaleY(getChildrenScale());
656
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800657 // Generate an id for each view, this assumes we have at most 256x256 cells
658 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700659 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700660 // If the horizontal or vertical span is set to -1, it is taken to
661 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700662 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
663 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800664
Winson Chungaafa03c2010-06-11 17:34:16 -0700665 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700666 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700667
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700668 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700669
Winson Chungaafa03c2010-06-11 17:34:16 -0700670 return true;
671 }
672 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800673 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700674
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800675 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700676 public void removeAllViews() {
677 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700678 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
680
681 @Override
682 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700684 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700685 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700686 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700687 }
688
689 @Override
690 public void removeView(View view) {
691 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700692 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700693 }
694
695 @Override
696 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700697 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
698 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700699 }
700
701 @Override
702 public void removeViewInLayout(View view) {
703 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700704 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700705 }
706
707 @Override
708 public void removeViews(int start, int count) {
709 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700710 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700711 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700712 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700713 }
714
715 @Override
716 public void removeViewsInLayout(int start, int count) {
717 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700718 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700719 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700720 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800721 }
722
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700723 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700724 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800725 * @param x X coordinate of the point
726 * @param y Y coordinate of the point
727 * @param result Array of 2 ints to hold the x and y coordinate of the cell
728 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700729 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700730 final int hStartPadding = getPaddingLeft();
731 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800732
733 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
734 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
735
Adam Cohend22015c2010-07-26 22:02:18 -0700736 final int xAxis = mCountX;
737 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800738
739 if (result[0] < 0) result[0] = 0;
740 if (result[0] >= xAxis) result[0] = xAxis - 1;
741 if (result[1] < 0) result[1] = 0;
742 if (result[1] >= yAxis) result[1] = yAxis - 1;
743 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700744
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800745 /**
746 * Given a point, return the cell that most closely encloses that point
747 * @param x X coordinate of the point
748 * @param y Y coordinate of the point
749 * @param result Array of 2 ints to hold the x and y coordinate of the cell
750 */
751 void pointToCellRounded(int x, int y, int[] result) {
752 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
753 }
754
755 /**
756 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700757 *
758 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800759 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700760 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800761 * @param result Array of 2 ints to hold the x and y coordinate of the point
762 */
763 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700764 final int hStartPadding = getPaddingLeft();
765 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800766
767 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
768 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
769 }
770
Adam Cohene3e27a82011-04-15 12:07:39 -0700771 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800772 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700773 *
774 * @param cellX X coordinate of the cell
775 * @param cellY Y coordinate of the cell
776 *
777 * @param result Array of 2 ints to hold the x and y coordinate of the point
778 */
779 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700780 regionToCenterPoint(cellX, cellY, 1, 1, result);
781 }
782
783 /**
784 * Given a cell coordinate and span return the point that represents the center of the regio
785 *
786 * @param cellX X coordinate of the cell
787 * @param cellY Y coordinate of the cell
788 *
789 * @param result Array of 2 ints to hold the x and y coordinate of the point
790 */
791 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700792 final int hStartPadding = getPaddingLeft();
793 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700794 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
795 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
796 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
797 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700798 }
799
Adam Cohen19f37922012-03-21 11:59:11 -0700800 /**
801 * Given a cell coordinate and span fills out a corresponding pixel rect
802 *
803 * @param cellX X coordinate of the cell
804 * @param cellY Y coordinate of the cell
805 * @param result Rect in which to write the result
806 */
807 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
808 final int hStartPadding = getPaddingLeft();
809 final int vStartPadding = getPaddingTop();
810 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
811 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
812 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
813 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
814 }
815
Adam Cohen482ed822012-03-02 14:15:13 -0800816 public float getDistanceFromCell(float x, float y, int[] cell) {
817 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700818 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800819 }
820
Romain Guy84f296c2009-11-04 15:00:44 -0800821 int getCellWidth() {
822 return mCellWidth;
823 }
824
825 int getCellHeight() {
826 return mCellHeight;
827 }
828
Adam Cohend4844c32011-02-18 19:25:06 -0800829 int getWidthGap() {
830 return mWidthGap;
831 }
832
833 int getHeightGap() {
834 return mHeightGap;
835 }
836
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700837 public void setFixedSize(int width, int height) {
838 mFixedWidth = width;
839 mFixedHeight = height;
840 }
841
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800842 @Override
843 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700844 LauncherAppState app = LauncherAppState.getInstance();
845 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
846
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800847 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800848 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700849 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
850 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700851 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
852 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700853 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700854 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
855 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700856 if (cw != mCellWidth || ch != mCellHeight) {
857 mCellWidth = cw;
858 mCellHeight = ch;
859 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
860 mHeightGap, mCountX, mCountY);
861 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700862 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700863
Winson Chung2d75f122013-09-23 16:53:31 -0700864 int newWidth = childWidthSize;
865 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700866 if (mFixedWidth > 0 && mFixedHeight > 0) {
867 newWidth = mFixedWidth;
868 newHeight = mFixedHeight;
869 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800870 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
871 }
872
Adam Cohend22015c2010-07-26 22:02:18 -0700873 int numWidthGaps = mCountX - 1;
874 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800875
Adam Cohen234c4cd2011-07-17 21:03:04 -0700876 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700877 int hSpace = childWidthSize;
878 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700879 int hFreeSpace = hSpace - (mCountX * mCellWidth);
880 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700881 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
882 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700883 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
884 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700885 } else {
886 mWidthGap = mOriginalWidthGap;
887 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700888 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700889
890 // Make the feedback view large enough to hold the blur bitmap.
891 mTouchFeedbackView.measure(
892 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
893 MeasureSpec.EXACTLY),
894 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
895 MeasureSpec.EXACTLY));
896
897 mShortcutsAndWidgets.measure(
898 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
899 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
900
901 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
902 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700903 if (mFixedWidth > 0 && mFixedHeight > 0) {
904 setMeasuredDimension(maxWidth, maxHeight);
905 } else {
906 setMeasuredDimension(widthSize, heightSize);
907 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908 }
909
910 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700911 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700912 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
913 (mCountX * mCellWidth);
914 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
915 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700916
917 mTouchFeedbackView.layout(left, top,
918 left + mTouchFeedbackView.getMeasuredWidth(),
919 top + mTouchFeedbackView.getMeasuredHeight());
920 mShortcutsAndWidgets.layout(left, top,
921 left + r - l,
922 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800923 }
924
925 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700926 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
927 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700928
929 // Expand the background drawing bounds by the padding baked into the background drawable
930 Rect padding = new Rect();
931 mNormalBackground.getPadding(padding);
932 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
933
Adam Cohenb5ba0972011-09-07 18:02:31 -0700934 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -0700935 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700936 }
937
938 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800939 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700940 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800941 }
942
943 @Override
944 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700945 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800946 }
947
Michael Jurka5f1c5092010-09-03 14:15:02 -0700948 public float getBackgroundAlpha() {
949 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700950 }
951
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700952 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -0700953 if (mBackgroundAlphaMultiplier != multiplier) {
954 mBackgroundAlphaMultiplier = multiplier;
955 invalidate();
956 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700957 }
958
Adam Cohenddb82192010-11-10 16:32:54 -0800959 public float getBackgroundAlphaMultiplier() {
960 return mBackgroundAlphaMultiplier;
961 }
962
Michael Jurka5f1c5092010-09-03 14:15:02 -0700963 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800964 if (mBackgroundAlpha != alpha) {
965 mBackgroundAlpha = alpha;
966 invalidate();
967 }
Michael Jurkadee05892010-07-27 10:01:56 -0700968 }
969
Michael Jurkaa52570f2012-03-20 03:18:20 -0700970 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700971 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700972 }
973
Michael Jurkaa52570f2012-03-20 03:18:20 -0700974 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700975 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700976 }
977
Patrick Dubroy440c3602010-07-13 17:50:32 -0700978 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700979 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700980 }
981
Adam Cohen76fc0852011-06-17 13:26:23 -0700982 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800983 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700984 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800985 boolean[][] occupied = mOccupied;
986 if (!permanent) {
987 occupied = mTmpOccupied;
988 }
989
Adam Cohen19f37922012-03-21 11:59:11 -0700990 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700991 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
992 final ItemInfo info = (ItemInfo) child.getTag();
993
994 // We cancel any existing animations
995 if (mReorderAnimators.containsKey(lp)) {
996 mReorderAnimators.get(lp).cancel();
997 mReorderAnimators.remove(lp);
998 }
999
Adam Cohen482ed822012-03-02 14:15:13 -08001000 final int oldX = lp.x;
1001 final int oldY = lp.y;
1002 if (adjustOccupied) {
1003 occupied[lp.cellX][lp.cellY] = false;
1004 occupied[cellX][cellY] = true;
1005 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001006 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001007 if (permanent) {
1008 lp.cellX = info.cellX = cellX;
1009 lp.cellY = info.cellY = cellY;
1010 } else {
1011 lp.tmpCellX = cellX;
1012 lp.tmpCellY = cellY;
1013 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001014 clc.setupLp(lp);
1015 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001016 final int newX = lp.x;
1017 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001018
Adam Cohen76fc0852011-06-17 13:26:23 -07001019 lp.x = oldX;
1020 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001021
Adam Cohen482ed822012-03-02 14:15:13 -08001022 // Exit early if we're not actually moving the view
1023 if (oldX == newX && oldY == newY) {
1024 lp.isLockedToGrid = true;
1025 return true;
1026 }
1027
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001028 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001029 va.setDuration(duration);
1030 mReorderAnimators.put(lp, va);
1031
1032 va.addUpdateListener(new AnimatorUpdateListener() {
1033 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001034 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001035 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001036 lp.x = (int) ((1 - r) * oldX + r * newX);
1037 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001038 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001039 }
1040 });
Adam Cohen482ed822012-03-02 14:15:13 -08001041 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001042 boolean cancelled = false;
1043 public void onAnimationEnd(Animator animation) {
1044 // If the animation was cancelled, it means that another animation
1045 // has interrupted this one, and we don't want to lock the item into
1046 // place just yet.
1047 if (!cancelled) {
1048 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001049 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001050 }
1051 if (mReorderAnimators.containsKey(lp)) {
1052 mReorderAnimators.remove(lp);
1053 }
1054 }
1055 public void onAnimationCancel(Animator animation) {
1056 cancelled = true;
1057 }
1058 });
Adam Cohen482ed822012-03-02 14:15:13 -08001059 va.setStartDelay(delay);
1060 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001061 return true;
1062 }
1063 return false;
1064 }
1065
Adam Cohen482ed822012-03-02 14:15:13 -08001066 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1067 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001068 final int oldDragCellX = mDragCell[0];
1069 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001070
Adam Cohen2801caf2011-05-13 20:57:39 -07001071 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001072 return;
1073 }
1074
Adam Cohen482ed822012-03-02 14:15:13 -08001075 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1076 mDragCell[0] = cellX;
1077 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001078 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001079 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001080 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001081
Joe Onorato4be866d2010-10-10 11:26:02 -07001082 int left = topLeft[0];
1083 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001084
Winson Chungb8c69f32011-10-19 21:36:08 -07001085 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001086 // When drawing the drag outline, it did not account for margin offsets
1087 // added by the view's parent.
1088 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1089 left += lp.leftMargin;
1090 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001091
Adam Cohen99e8b402011-03-25 19:23:43 -07001092 // Offsets due to the size difference between the View and the dragOutline.
1093 // There is a size difference to account for the outer blur, which may lie
1094 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001095 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001096 // We center about the x axis
1097 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1098 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001099 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001100 if (dragOffset != null && dragRegion != null) {
1101 // Center the drag region *horizontally* in the cell and apply a drag
1102 // outline offset
1103 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1104 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001105 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1106 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1107 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001108 } else {
1109 // Center the drag outline in the cell
1110 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1111 - dragOutline.getWidth()) / 2;
1112 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1113 - dragOutline.getHeight()) / 2;
1114 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001115 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001116 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001117 mDragOutlineAnims[oldIndex].animateOut();
1118 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001119 Rect r = mDragOutlines[mDragOutlineCurrent];
1120 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1121 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001122 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001123 }
Winson Chung150fbab2010-09-29 17:14:26 -07001124
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001125 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1126 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001127 }
1128 }
1129
Adam Cohene0310962011-04-18 16:15:31 -07001130 public void clearDragOutlines() {
1131 final int oldIndex = mDragOutlineCurrent;
1132 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001133 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001134 }
1135
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001136 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001137 * Find a vacant area that will fit the given bounds nearest the requested
1138 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001139 *
Romain Guy51afc022009-05-04 18:03:43 -07001140 * @param pixelX The X location at which you want to search for a vacant area.
1141 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001142 * @param spanX Horizontal span of the object.
1143 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001144 * @param result Array in which to place the result, or null (in which case a new array will
1145 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001146 * @return The X, Y cell of a vacant area that can contain this object,
1147 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001148 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001149 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1150 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001151 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001152
Michael Jurka6a1435d2010-09-27 17:35:12 -07001153 /**
1154 * Find a vacant area that will fit the given bounds nearest the requested
1155 * cell location. Uses Euclidean distance to score multiple vacant areas.
1156 *
1157 * @param pixelX The X location at which you want to search for a vacant area.
1158 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001159 * @param minSpanX The minimum horizontal span required
1160 * @param minSpanY The minimum vertical span required
1161 * @param spanX Horizontal span of the object.
1162 * @param spanY Vertical span of the object.
1163 * @param result Array in which to place the result, or null (in which case a new array will
1164 * be allocated)
1165 * @return The X, Y cell of a vacant area that can contain this object,
1166 * nearest the requested location.
1167 */
1168 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1169 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001170 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001171 result, resultSpan);
1172 }
1173
Adam Cohend41fbf52012-02-16 23:53:59 -08001174 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1175 private void lazyInitTempRectStack() {
1176 if (mTempRectStack.isEmpty()) {
1177 for (int i = 0; i < mCountX * mCountY; i++) {
1178 mTempRectStack.push(new Rect());
1179 }
1180 }
1181 }
Adam Cohen482ed822012-03-02 14:15:13 -08001182
Adam Cohend41fbf52012-02-16 23:53:59 -08001183 private void recycleTempRects(Stack<Rect> used) {
1184 while (!used.isEmpty()) {
1185 mTempRectStack.push(used.pop());
1186 }
1187 }
1188
1189 /**
1190 * Find a vacant area that will fit the given bounds nearest the requested
1191 * cell location. Uses Euclidean distance to score multiple vacant areas.
1192 *
1193 * @param pixelX The X location at which you want to search for a vacant area.
1194 * @param pixelY The Y location at which you want to search for a vacant area.
1195 * @param minSpanX The minimum horizontal span required
1196 * @param minSpanY The minimum vertical span required
1197 * @param spanX Horizontal span of the object.
1198 * @param spanY Vertical span of the object.
1199 * @param ignoreOccupied If true, the result can be an occupied cell
1200 * @param result Array in which to place the result, or null (in which case a new array will
1201 * be allocated)
1202 * @return The X, Y cell of a vacant area that can contain this object,
1203 * nearest the requested location.
1204 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001205 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1206 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001207 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001208
Adam Cohene3e27a82011-04-15 12:07:39 -07001209 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1210 // to the center of the item, but we are searching based on the top-left cell, so
1211 // we translate the point over to correspond to the top-left.
1212 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1213 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1214
Jeff Sharkey70864282009-04-07 21:08:40 -07001215 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001216 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001217 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001218 final Rect bestRect = new Rect(-1, -1, -1, -1);
1219 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001220
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001221 final int countX = mCountX;
1222 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001223
Adam Cohend41fbf52012-02-16 23:53:59 -08001224 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1225 spanX < minSpanX || spanY < minSpanY) {
1226 return bestXY;
1227 }
1228
1229 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001230 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001231 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1232 int ySize = -1;
1233 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001234 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001235 // First, let's see if this thing fits anywhere
1236 for (int i = 0; i < minSpanX; i++) {
1237 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001238 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001239 continue inner;
1240 }
Michael Jurkac28de512010-08-13 11:27:44 -07001241 }
1242 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001243 xSize = minSpanX;
1244 ySize = minSpanY;
1245
1246 // We know that the item will fit at _some_ acceptable size, now let's see
1247 // how big we can make it. We'll alternate between incrementing x and y spans
1248 // until we hit a limit.
1249 boolean incX = true;
1250 boolean hitMaxX = xSize >= spanX;
1251 boolean hitMaxY = ySize >= spanY;
1252 while (!(hitMaxX && hitMaxY)) {
1253 if (incX && !hitMaxX) {
1254 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001255 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001256 // We can't move out horizontally
1257 hitMaxX = true;
1258 }
1259 }
1260 if (!hitMaxX) {
1261 xSize++;
1262 }
1263 } else if (!hitMaxY) {
1264 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001265 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001266 // We can't move out vertically
1267 hitMaxY = true;
1268 }
1269 }
1270 if (!hitMaxY) {
1271 ySize++;
1272 }
1273 }
1274 hitMaxX |= xSize >= spanX;
1275 hitMaxY |= ySize >= spanY;
1276 incX = !incX;
1277 }
1278 incX = true;
1279 hitMaxX = xSize >= spanX;
1280 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001281 }
Winson Chung0be025d2011-05-23 17:45:09 -07001282 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001283 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001284
Adam Cohend41fbf52012-02-16 23:53:59 -08001285 // We verify that the current rect is not a sub-rect of any of our previous
1286 // candidates. In this case, the current rect is disqualified in favour of the
1287 // containing rect.
1288 Rect currentRect = mTempRectStack.pop();
1289 currentRect.set(x, y, x + xSize, y + ySize);
1290 boolean contained = false;
1291 for (Rect r : validRegions) {
1292 if (r.contains(currentRect)) {
1293 contained = true;
1294 break;
1295 }
1296 }
1297 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001298 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001299
Adam Cohend41fbf52012-02-16 23:53:59 -08001300 if ((distance <= bestDistance && !contained) ||
1301 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001302 bestDistance = distance;
1303 bestXY[0] = x;
1304 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001305 if (resultSpan != null) {
1306 resultSpan[0] = xSize;
1307 resultSpan[1] = ySize;
1308 }
1309 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001310 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001311 }
1312 }
1313
Adam Cohenc0dcf592011-06-01 15:30:43 -07001314 // Return -1, -1 if no suitable location found
1315 if (bestDistance == Double.MAX_VALUE) {
1316 bestXY[0] = -1;
1317 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001318 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001319 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001320 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001321 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001322
Adam Cohen482ed822012-03-02 14:15:13 -08001323 /**
1324 * Find a vacant area that will fit the given bounds nearest the requested
1325 * cell location, and will also weigh in a suggested direction vector of the
1326 * desired location. This method computers distance based on unit grid distances,
1327 * not pixel distances.
1328 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001329 * @param cellX The X cell nearest to which you want to search for a vacant area.
1330 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001331 * @param spanX Horizontal span of the object.
1332 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001333 * @param direction The favored direction in which the views should move from x, y
1334 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1335 * matches exactly. Otherwise we find the best matching direction.
1336 * @param occoupied The array which represents which cells in the CellLayout are occupied
1337 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001338 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001339 * @param result Array in which to place the result, or null (in which case a new array will
1340 * be allocated)
1341 * @return The X, Y cell of a vacant area that can contain this object,
1342 * nearest the requested location.
1343 */
1344 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001345 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001346 // Keep track of best-scoring drop area
1347 final int[] bestXY = result != null ? result : new int[2];
1348 float bestDistance = Float.MAX_VALUE;
1349 int bestDirectionScore = Integer.MIN_VALUE;
1350
1351 final int countX = mCountX;
1352 final int countY = mCountY;
1353
1354 for (int y = 0; y < countY - (spanY - 1); y++) {
1355 inner:
1356 for (int x = 0; x < countX - (spanX - 1); x++) {
1357 // First, let's see if this thing fits anywhere
1358 for (int i = 0; i < spanX; i++) {
1359 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001360 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001361 continue inner;
1362 }
1363 }
1364 }
1365
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001366 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001367 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001368 computeDirectionVector(x - cellX, y - cellY, curDirection);
1369 // The direction score is just the dot product of the two candidate direction
1370 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001371 int curDirectionScore = direction[0] * curDirection[0] +
1372 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001373 boolean exactDirectionOnly = false;
1374 boolean directionMatches = direction[0] == curDirection[0] &&
1375 direction[0] == curDirection[0];
1376 if ((directionMatches || !exactDirectionOnly) &&
1377 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001378 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1379 bestDistance = distance;
1380 bestDirectionScore = curDirectionScore;
1381 bestXY[0] = x;
1382 bestXY[1] = y;
1383 }
1384 }
1385 }
1386
1387 // Return -1, -1 if no suitable location found
1388 if (bestDistance == Float.MAX_VALUE) {
1389 bestXY[0] = -1;
1390 bestXY[1] = -1;
1391 }
1392 return bestXY;
1393 }
1394
1395 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001396 int[] direction, ItemConfiguration currentState) {
1397 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001398 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001399 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001400 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1401
Adam Cohen8baab352012-03-20 17:39:21 -07001402 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001403
1404 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001405 c.x = mTempLocation[0];
1406 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001407 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001408 }
Adam Cohen8baab352012-03-20 17:39:21 -07001409 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001410 return success;
1411 }
1412
Adam Cohenf3900c22012-11-16 18:28:11 -08001413 /**
1414 * This helper class defines a cluster of views. It helps with defining complex edges
1415 * of the cluster and determining how those edges interact with other views. The edges
1416 * essentially define a fine-grained boundary around the cluster of views -- like a more
1417 * precise version of a bounding box.
1418 */
1419 private class ViewCluster {
1420 final static int LEFT = 0;
1421 final static int TOP = 1;
1422 final static int RIGHT = 2;
1423 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001424
Adam Cohenf3900c22012-11-16 18:28:11 -08001425 ArrayList<View> views;
1426 ItemConfiguration config;
1427 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001428
Adam Cohenf3900c22012-11-16 18:28:11 -08001429 int[] leftEdge = new int[mCountY];
1430 int[] rightEdge = new int[mCountY];
1431 int[] topEdge = new int[mCountX];
1432 int[] bottomEdge = new int[mCountX];
1433 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1434
1435 @SuppressWarnings("unchecked")
1436 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1437 this.views = (ArrayList<View>) views.clone();
1438 this.config = config;
1439 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001440 }
1441
Adam Cohenf3900c22012-11-16 18:28:11 -08001442 void resetEdges() {
1443 for (int i = 0; i < mCountX; i++) {
1444 topEdge[i] = -1;
1445 bottomEdge[i] = -1;
1446 }
1447 for (int i = 0; i < mCountY; i++) {
1448 leftEdge[i] = -1;
1449 rightEdge[i] = -1;
1450 }
1451 leftEdgeDirty = true;
1452 rightEdgeDirty = true;
1453 bottomEdgeDirty = true;
1454 topEdgeDirty = true;
1455 boundingRectDirty = true;
1456 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001457
Adam Cohenf3900c22012-11-16 18:28:11 -08001458 void computeEdge(int which, int[] edge) {
1459 int count = views.size();
1460 for (int i = 0; i < count; i++) {
1461 CellAndSpan cs = config.map.get(views.get(i));
1462 switch (which) {
1463 case LEFT:
1464 int left = cs.x;
1465 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1466 if (left < edge[j] || edge[j] < 0) {
1467 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001468 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001469 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001470 break;
1471 case RIGHT:
1472 int right = cs.x + cs.spanX;
1473 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1474 if (right > edge[j]) {
1475 edge[j] = right;
1476 }
1477 }
1478 break;
1479 case TOP:
1480 int top = cs.y;
1481 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1482 if (top < edge[j] || edge[j] < 0) {
1483 edge[j] = top;
1484 }
1485 }
1486 break;
1487 case BOTTOM:
1488 int bottom = cs.y + cs.spanY;
1489 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1490 if (bottom > edge[j]) {
1491 edge[j] = bottom;
1492 }
1493 }
1494 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001495 }
1496 }
1497 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001498
1499 boolean isViewTouchingEdge(View v, int whichEdge) {
1500 CellAndSpan cs = config.map.get(v);
1501
1502 int[] edge = getEdge(whichEdge);
1503
1504 switch (whichEdge) {
1505 case LEFT:
1506 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1507 if (edge[i] == cs.x + cs.spanX) {
1508 return true;
1509 }
1510 }
1511 break;
1512 case RIGHT:
1513 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1514 if (edge[i] == cs.x) {
1515 return true;
1516 }
1517 }
1518 break;
1519 case TOP:
1520 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1521 if (edge[i] == cs.y + cs.spanY) {
1522 return true;
1523 }
1524 }
1525 break;
1526 case BOTTOM:
1527 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1528 if (edge[i] == cs.y) {
1529 return true;
1530 }
1531 }
1532 break;
1533 }
1534 return false;
1535 }
1536
1537 void shift(int whichEdge, int delta) {
1538 for (View v: views) {
1539 CellAndSpan c = config.map.get(v);
1540 switch (whichEdge) {
1541 case LEFT:
1542 c.x -= delta;
1543 break;
1544 case RIGHT:
1545 c.x += delta;
1546 break;
1547 case TOP:
1548 c.y -= delta;
1549 break;
1550 case BOTTOM:
1551 default:
1552 c.y += delta;
1553 break;
1554 }
1555 }
1556 resetEdges();
1557 }
1558
1559 public void addView(View v) {
1560 views.add(v);
1561 resetEdges();
1562 }
1563
1564 public Rect getBoundingRect() {
1565 if (boundingRectDirty) {
1566 boolean first = true;
1567 for (View v: views) {
1568 CellAndSpan c = config.map.get(v);
1569 if (first) {
1570 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1571 first = false;
1572 } else {
1573 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1574 }
1575 }
1576 }
1577 return boundingRect;
1578 }
1579
1580 public int[] getEdge(int which) {
1581 switch (which) {
1582 case LEFT:
1583 return getLeftEdge();
1584 case RIGHT:
1585 return getRightEdge();
1586 case TOP:
1587 return getTopEdge();
1588 case BOTTOM:
1589 default:
1590 return getBottomEdge();
1591 }
1592 }
1593
1594 public int[] getLeftEdge() {
1595 if (leftEdgeDirty) {
1596 computeEdge(LEFT, leftEdge);
1597 }
1598 return leftEdge;
1599 }
1600
1601 public int[] getRightEdge() {
1602 if (rightEdgeDirty) {
1603 computeEdge(RIGHT, rightEdge);
1604 }
1605 return rightEdge;
1606 }
1607
1608 public int[] getTopEdge() {
1609 if (topEdgeDirty) {
1610 computeEdge(TOP, topEdge);
1611 }
1612 return topEdge;
1613 }
1614
1615 public int[] getBottomEdge() {
1616 if (bottomEdgeDirty) {
1617 computeEdge(BOTTOM, bottomEdge);
1618 }
1619 return bottomEdge;
1620 }
1621
1622 PositionComparator comparator = new PositionComparator();
1623 class PositionComparator implements Comparator<View> {
1624 int whichEdge = 0;
1625 public int compare(View left, View right) {
1626 CellAndSpan l = config.map.get(left);
1627 CellAndSpan r = config.map.get(right);
1628 switch (whichEdge) {
1629 case LEFT:
1630 return (r.x + r.spanX) - (l.x + l.spanX);
1631 case RIGHT:
1632 return l.x - r.x;
1633 case TOP:
1634 return (r.y + r.spanY) - (l.y + l.spanY);
1635 case BOTTOM:
1636 default:
1637 return l.y - r.y;
1638 }
1639 }
1640 }
1641
1642 public void sortConfigurationForEdgePush(int edge) {
1643 comparator.whichEdge = edge;
1644 Collections.sort(config.sortedViews, comparator);
1645 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001646 }
1647
Adam Cohenf3900c22012-11-16 18:28:11 -08001648 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1649 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001650
Adam Cohenf3900c22012-11-16 18:28:11 -08001651 ViewCluster cluster = new ViewCluster(views, currentState);
1652 Rect clusterRect = cluster.getBoundingRect();
1653 int whichEdge;
1654 int pushDistance;
1655 boolean fail = false;
1656
1657 // Determine the edge of the cluster that will be leading the push and how far
1658 // the cluster must be shifted.
1659 if (direction[0] < 0) {
1660 whichEdge = ViewCluster.LEFT;
1661 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001662 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001663 whichEdge = ViewCluster.RIGHT;
1664 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1665 } else if (direction[1] < 0) {
1666 whichEdge = ViewCluster.TOP;
1667 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1668 } else {
1669 whichEdge = ViewCluster.BOTTOM;
1670 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001671 }
1672
Adam Cohenf3900c22012-11-16 18:28:11 -08001673 // Break early for invalid push distance.
1674 if (pushDistance <= 0) {
1675 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001676 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001677
1678 // Mark the occupied state as false for the group of views we want to move.
1679 for (View v: views) {
1680 CellAndSpan c = currentState.map.get(v);
1681 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1682 }
1683
1684 // We save the current configuration -- if we fail to find a solution we will revert
1685 // to the initial state. The process of finding a solution modifies the configuration
1686 // in place, hence the need for revert in the failure case.
1687 currentState.save();
1688
1689 // The pushing algorithm is simplified by considering the views in the order in which
1690 // they would be pushed by the cluster. For example, if the cluster is leading with its
1691 // left edge, we consider sort the views by their right edge, from right to left.
1692 cluster.sortConfigurationForEdgePush(whichEdge);
1693
1694 while (pushDistance > 0 && !fail) {
1695 for (View v: currentState.sortedViews) {
1696 // For each view that isn't in the cluster, we see if the leading edge of the
1697 // cluster is contacting the edge of that view. If so, we add that view to the
1698 // cluster.
1699 if (!cluster.views.contains(v) && v != dragView) {
1700 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1701 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1702 if (!lp.canReorder) {
1703 // The push solution includes the all apps button, this is not viable.
1704 fail = true;
1705 break;
1706 }
1707 cluster.addView(v);
1708 CellAndSpan c = currentState.map.get(v);
1709
1710 // Adding view to cluster, mark it as not occupied.
1711 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1712 }
1713 }
1714 }
1715 pushDistance--;
1716
1717 // The cluster has been completed, now we move the whole thing over in the appropriate
1718 // direction.
1719 cluster.shift(whichEdge, 1);
1720 }
1721
1722 boolean foundSolution = false;
1723 clusterRect = cluster.getBoundingRect();
1724
1725 // Due to the nature of the algorithm, the only check required to verify a valid solution
1726 // is to ensure that completed shifted cluster lies completely within the cell layout.
1727 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1728 clusterRect.bottom <= mCountY) {
1729 foundSolution = true;
1730 } else {
1731 currentState.restore();
1732 }
1733
1734 // In either case, we set the occupied array as marked for the location of the views
1735 for (View v: cluster.views) {
1736 CellAndSpan c = currentState.map.get(v);
1737 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1738 }
1739
1740 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001741 }
1742
Adam Cohen482ed822012-03-02 14:15:13 -08001743 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001744 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001745 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001746
Adam Cohen8baab352012-03-20 17:39:21 -07001747 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001748 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001749 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001750 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001751 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001752 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001753 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001754 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001755 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001756 }
1757 }
Adam Cohen8baab352012-03-20 17:39:21 -07001758
Adam Cohen8baab352012-03-20 17:39:21 -07001759 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001760 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001761 CellAndSpan c = currentState.map.get(v);
1762 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1763 }
1764
Adam Cohen47a876d2012-03-19 13:21:41 -07001765 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1766 int top = boundingRect.top;
1767 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001768 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001769 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001770 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001771 CellAndSpan c = currentState.map.get(v);
1772 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001773 }
1774
Adam Cohen482ed822012-03-02 14:15:13 -08001775 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1776
Adam Cohenf3900c22012-11-16 18:28:11 -08001777 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1778 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001779
Adam Cohen8baab352012-03-20 17:39:21 -07001780 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001781 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001782 int deltaX = mTempLocation[0] - boundingRect.left;
1783 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001784 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001785 CellAndSpan c = currentState.map.get(v);
1786 c.x += deltaX;
1787 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001788 }
1789 success = true;
1790 }
Adam Cohen8baab352012-03-20 17:39:21 -07001791
1792 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001793 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001794 CellAndSpan c = currentState.map.get(v);
1795 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001796 }
1797 return success;
1798 }
1799
1800 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1801 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1802 }
1803
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001804 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1805 // to push items in each of the cardinal directions, in an order based on the direction vector
1806 // passed.
1807 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1808 int[] direction, View ignoreView, ItemConfiguration solution) {
1809 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001810 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001811 // separately in each of the components.
1812 int temp = direction[1];
1813 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001814
1815 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001816 ignoreView, solution)) {
1817 return true;
1818 }
1819 direction[1] = temp;
1820 temp = direction[0];
1821 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001822
1823 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001824 ignoreView, solution)) {
1825 return true;
1826 }
1827 // Revert the direction
1828 direction[0] = temp;
1829
1830 // Now we try pushing in each component of the opposite direction
1831 direction[0] *= -1;
1832 direction[1] *= -1;
1833 temp = direction[1];
1834 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001835 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001836 ignoreView, solution)) {
1837 return true;
1838 }
1839
1840 direction[1] = temp;
1841 temp = direction[0];
1842 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001843 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001844 ignoreView, solution)) {
1845 return true;
1846 }
1847 // revert the direction
1848 direction[0] = temp;
1849 direction[0] *= -1;
1850 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001851
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001852 } else {
1853 // If the direction vector has a single non-zero component, we push first in the
1854 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001855 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001856 ignoreView, solution)) {
1857 return true;
1858 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001859 // Then we try the opposite direction
1860 direction[0] *= -1;
1861 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001862 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001863 ignoreView, solution)) {
1864 return true;
1865 }
1866 // Switch the direction back
1867 direction[0] *= -1;
1868 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001869
1870 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001871 // to find a solution by pushing along the perpendicular axis.
1872
1873 // Swap the components
1874 int temp = direction[1];
1875 direction[1] = direction[0];
1876 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001877 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001878 ignoreView, solution)) {
1879 return true;
1880 }
1881
1882 // Then we try the opposite direction
1883 direction[0] *= -1;
1884 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001885 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001886 ignoreView, solution)) {
1887 return true;
1888 }
1889 // Switch the direction back
1890 direction[0] *= -1;
1891 direction[1] *= -1;
1892
1893 // Swap the components back
1894 temp = direction[1];
1895 direction[1] = direction[0];
1896 direction[0] = temp;
1897 }
1898 return false;
1899 }
1900
Adam Cohen482ed822012-03-02 14:15:13 -08001901 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001902 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001903 // Return early if get invalid cell positions
1904 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001905
Adam Cohen8baab352012-03-20 17:39:21 -07001906 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001907 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001908
Adam Cohen8baab352012-03-20 17:39:21 -07001909 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001910 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001911 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001912 if (c != null) {
1913 c.x = cellX;
1914 c.y = cellY;
1915 }
Adam Cohen482ed822012-03-02 14:15:13 -08001916 }
Adam Cohen482ed822012-03-02 14:15:13 -08001917 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1918 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001919 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001920 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001921 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001922 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001923 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001924 if (Rect.intersects(r0, r1)) {
1925 if (!lp.canReorder) {
1926 return false;
1927 }
1928 mIntersectingViews.add(child);
1929 }
1930 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001931
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001932 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1933
Winson Chung5f8afe62013-08-12 16:19:28 -07001934 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001935 // we try to find a solution such that no displaced item travels through another item
1936 // without also displacing that item.
1937 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001938 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001939 return true;
1940 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001941
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001942 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001943 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001944 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001945 return true;
1946 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001947
Adam Cohen482ed822012-03-02 14:15:13 -08001948 // Ok, they couldn't move as a block, let's move them individually
1949 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001950 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001951 return false;
1952 }
1953 }
1954 return true;
1955 }
1956
1957 /*
1958 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1959 * the provided point and the provided cell
1960 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001961 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001962 double angle = Math.atan(((float) deltaY) / deltaX);
1963
1964 result[0] = 0;
1965 result[1] = 0;
1966 if (Math.abs(Math.cos(angle)) > 0.5f) {
1967 result[0] = (int) Math.signum(deltaX);
1968 }
1969 if (Math.abs(Math.sin(angle)) > 0.5f) {
1970 result[1] = (int) Math.signum(deltaY);
1971 }
1972 }
1973
Adam Cohen8baab352012-03-20 17:39:21 -07001974 private void copyOccupiedArray(boolean[][] occupied) {
1975 for (int i = 0; i < mCountX; i++) {
1976 for (int j = 0; j < mCountY; j++) {
1977 occupied[i][j] = mOccupied[i][j];
1978 }
1979 }
1980 }
1981
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001982 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001983 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1984 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001985 // Copy the current state into the solution. This solution will be manipulated as necessary.
1986 copyCurrentStateToSolution(solution, false);
1987 // Copy the current occupied array into the temporary occupied array. This array will be
1988 // manipulated as necessary to find a solution.
1989 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001990
1991 // We find the nearest cell into which we would place the dragged item, assuming there's
1992 // nothing in its way.
1993 int result[] = new int[2];
1994 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1995
1996 boolean success = false;
1997 // First we try the exact nearest position of the item being dragged,
1998 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001999 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2000 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002001
2002 if (!success) {
2003 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2004 // x, then 1 in y etc.
2005 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002006 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2007 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002008 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002009 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2010 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002011 }
2012 solution.isSolution = false;
2013 } else {
2014 solution.isSolution = true;
2015 solution.dragViewX = result[0];
2016 solution.dragViewY = result[1];
2017 solution.dragViewSpanX = spanX;
2018 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002019 }
2020 return solution;
2021 }
2022
2023 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002024 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002025 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002026 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002027 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002028 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002029 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002030 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002031 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002032 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002033 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002034 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002035 }
2036 }
2037
2038 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2039 for (int i = 0; i < mCountX; i++) {
2040 for (int j = 0; j < mCountY; j++) {
2041 mTmpOccupied[i][j] = false;
2042 }
2043 }
2044
Michael Jurkaa52570f2012-03-20 03:18:20 -07002045 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002046 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002047 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002048 if (child == dragView) continue;
2049 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002050 CellAndSpan c = solution.map.get(child);
2051 if (c != null) {
2052 lp.tmpCellX = c.x;
2053 lp.tmpCellY = c.y;
2054 lp.cellHSpan = c.spanX;
2055 lp.cellVSpan = c.spanY;
2056 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002057 }
2058 }
2059 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2060 solution.dragViewSpanY, mTmpOccupied, true);
2061 }
2062
2063 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2064 commitDragView) {
2065
2066 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2067 for (int i = 0; i < mCountX; i++) {
2068 for (int j = 0; j < mCountY; j++) {
2069 occupied[i][j] = false;
2070 }
2071 }
2072
Michael Jurkaa52570f2012-03-20 03:18:20 -07002073 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002074 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002075 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002076 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002077 CellAndSpan c = solution.map.get(child);
2078 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002079 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2080 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002081 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002082 }
2083 }
2084 if (commitDragView) {
2085 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2086 solution.dragViewSpanY, occupied, true);
2087 }
2088 }
2089
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002090
2091 // This method starts or changes the reorder preview animations
2092 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2093 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002094 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002095 for (int i = 0; i < childCount; i++) {
2096 View child = mShortcutsAndWidgets.getChildAt(i);
2097 if (child == dragView) continue;
2098 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002099 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2100 != null && !solution.intersectingViews.contains(child);
2101
Adam Cohen19f37922012-03-21 11:59:11 -07002102 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002103 if (c != null && !skip) {
2104 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2105 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002106 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002107 }
2108 }
2109 }
2110
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002111 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002112 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002113 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002114 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002115 float finalDeltaX;
2116 float finalDeltaY;
2117 float initDeltaX;
2118 float initDeltaY;
2119 float finalScale;
2120 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002121 int mode;
2122 boolean repeating = false;
2123 private static final int PREVIEW_DURATION = 300;
2124 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2125
2126 public static final int MODE_HINT = 0;
2127 public static final int MODE_PREVIEW = 1;
2128
Adam Cohene7587d22012-05-24 18:50:02 -07002129 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002130
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002131 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2132 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002133 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2134 final int x0 = mTmpPoint[0];
2135 final int y0 = mTmpPoint[1];
2136 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2137 final int x1 = mTmpPoint[0];
2138 final int y1 = mTmpPoint[1];
2139 final int dX = x1 - x0;
2140 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002141 finalDeltaX = 0;
2142 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002143 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002144 if (dX == dY && dX == 0) {
2145 } else {
2146 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002147 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002148 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002149 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002150 } else {
2151 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002152 finalDeltaX = (int) (- dir * Math.signum(dX) *
2153 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2154 finalDeltaY = (int) (- dir * Math.signum(dY) *
2155 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002156 }
2157 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002158 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002159 initDeltaX = child.getTranslationX();
2160 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002161 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002162 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002163 this.child = child;
2164 }
2165
Adam Cohend024f982012-05-23 18:26:45 -07002166 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002167 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002168 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002169 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002170 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002171 if (finalDeltaX == 0 && finalDeltaY == 0) {
2172 completeAnimationImmediately();
2173 return;
2174 }
Adam Cohen19f37922012-03-21 11:59:11 -07002175 }
Adam Cohend024f982012-05-23 18:26:45 -07002176 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002177 return;
2178 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002179 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002180 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002181 va.setRepeatMode(ValueAnimator.REVERSE);
2182 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002183 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002184 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002185 va.addUpdateListener(new AnimatorUpdateListener() {
2186 @Override
2187 public void onAnimationUpdate(ValueAnimator animation) {
2188 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002189 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2190 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2191 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002192 child.setTranslationX(x);
2193 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002194 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002195 child.setScaleX(s);
2196 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002197 }
2198 });
2199 va.addListener(new AnimatorListenerAdapter() {
2200 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002201 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002202 initDeltaX = 0;
2203 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002204 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002205 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002206 }
2207 });
Adam Cohen19f37922012-03-21 11:59:11 -07002208 mShakeAnimators.put(child, this);
2209 va.start();
2210 }
2211
Adam Cohend024f982012-05-23 18:26:45 -07002212 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002213 if (a != null) {
2214 a.cancel();
2215 }
Adam Cohen19f37922012-03-21 11:59:11 -07002216 }
Adam Cohene7587d22012-05-24 18:50:02 -07002217
Adam Cohen091440a2015-03-18 14:16:05 -07002218 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002219 if (a != null) {
2220 a.cancel();
2221 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002222
Michael Jurka2ecf9952012-06-18 12:52:28 -07002223 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002224 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002225 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002226 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2227 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002228 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2229 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002230 );
2231 s.setDuration(REORDER_ANIMATION_DURATION);
2232 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2233 s.start();
2234 }
Adam Cohen19f37922012-03-21 11:59:11 -07002235 }
2236
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002237 private void completeAndClearReorderPreviewAnimations() {
2238 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002239 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002240 }
2241 mShakeAnimators.clear();
2242 }
2243
Adam Cohen482ed822012-03-02 14:15:13 -08002244 private void commitTempPlacement() {
2245 for (int i = 0; i < mCountX; i++) {
2246 for (int j = 0; j < mCountY; j++) {
2247 mOccupied[i][j] = mTmpOccupied[i][j];
2248 }
2249 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002250 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002251 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002252 View child = mShortcutsAndWidgets.getChildAt(i);
2253 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2254 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002255 // We do a null check here because the item info can be null in the case of the
2256 // AllApps button in the hotseat.
2257 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002258 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2259 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2260 info.requiresDbUpdate = true;
2261 }
Adam Cohen2acce882012-03-28 19:03:19 -07002262 info.cellX = lp.cellX = lp.tmpCellX;
2263 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002264 info.spanX = lp.cellHSpan;
2265 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002266 }
Adam Cohen482ed822012-03-02 14:15:13 -08002267 }
Adam Cohen2acce882012-03-28 19:03:19 -07002268 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002269 }
2270
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002271 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002272 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002273 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002274 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002275 lp.useTmpCoords = useTempCoords;
2276 }
2277 }
2278
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002279 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002280 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2281 int[] result = new int[2];
2282 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002283 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002284 resultSpan);
2285 if (result[0] >= 0 && result[1] >= 0) {
2286 copyCurrentStateToSolution(solution, false);
2287 solution.dragViewX = result[0];
2288 solution.dragViewY = result[1];
2289 solution.dragViewSpanX = resultSpan[0];
2290 solution.dragViewSpanY = resultSpan[1];
2291 solution.isSolution = true;
2292 } else {
2293 solution.isSolution = false;
2294 }
2295 return solution;
2296 }
2297
2298 public void prepareChildForDrag(View child) {
2299 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002300 }
2301
Adam Cohen19f37922012-03-21 11:59:11 -07002302 /* This seems like it should be obvious and straight-forward, but when the direction vector
2303 needs to match with the notion of the dragView pushing other views, we have to employ
2304 a slightly more subtle notion of the direction vector. The question is what two points is
2305 the vector between? The center of the dragView and its desired destination? Not quite, as
2306 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2307 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2308 or right, which helps make pushing feel right.
2309 */
2310 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2311 int spanY, View dragView, int[] resultDirection) {
2312 int[] targetDestination = new int[2];
2313
2314 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2315 Rect dragRect = new Rect();
2316 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2317 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2318
2319 Rect dropRegionRect = new Rect();
2320 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2321 dragView, dropRegionRect, mIntersectingViews);
2322
2323 int dropRegionSpanX = dropRegionRect.width();
2324 int dropRegionSpanY = dropRegionRect.height();
2325
2326 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2327 dropRegionRect.height(), dropRegionRect);
2328
2329 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2330 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2331
2332 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2333 deltaX = 0;
2334 }
2335 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2336 deltaY = 0;
2337 }
2338
2339 if (deltaX == 0 && deltaY == 0) {
2340 // No idea what to do, give a random direction.
2341 resultDirection[0] = 1;
2342 resultDirection[1] = 0;
2343 } else {
2344 computeDirectionVector(deltaX, deltaY, resultDirection);
2345 }
2346 }
2347
2348 // For a given cell and span, fetch the set of views intersecting the region.
2349 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2350 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2351 if (boundingRect != null) {
2352 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2353 }
2354 intersectingViews.clear();
2355 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2356 Rect r1 = new Rect();
2357 final int count = mShortcutsAndWidgets.getChildCount();
2358 for (int i = 0; i < count; i++) {
2359 View child = mShortcutsAndWidgets.getChildAt(i);
2360 if (child == dragView) continue;
2361 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2362 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2363 if (Rect.intersects(r0, r1)) {
2364 mIntersectingViews.add(child);
2365 if (boundingRect != null) {
2366 boundingRect.union(r1);
2367 }
2368 }
2369 }
2370 }
2371
2372 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2373 View dragView, int[] result) {
2374 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2375 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2376 mIntersectingViews);
2377 return !mIntersectingViews.isEmpty();
2378 }
2379
2380 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002381 completeAndClearReorderPreviewAnimations();
2382 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2383 final int count = mShortcutsAndWidgets.getChildCount();
2384 for (int i = 0; i < count; i++) {
2385 View child = mShortcutsAndWidgets.getChildAt(i);
2386 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2387 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2388 lp.tmpCellX = lp.cellX;
2389 lp.tmpCellY = lp.cellY;
2390 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2391 0, false, false);
2392 }
Adam Cohen19f37922012-03-21 11:59:11 -07002393 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002394 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002395 }
Adam Cohen19f37922012-03-21 11:59:11 -07002396 }
2397
Adam Cohenbebf0422012-04-11 18:06:28 -07002398 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2399 View dragView, int[] direction, boolean commit) {
2400 int[] pixelXY = new int[2];
2401 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2402
2403 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002404 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002405 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2406
2407 setUseTempCoords(true);
2408 if (swapSolution != null && swapSolution.isSolution) {
2409 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2410 // committing anything or animating anything as we just want to determine if a solution
2411 // exists
2412 copySolutionToTempState(swapSolution, dragView);
2413 setItemPlacementDirty(true);
2414 animateItemsToSolution(swapSolution, dragView, commit);
2415
2416 if (commit) {
2417 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002418 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002419 setItemPlacementDirty(false);
2420 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002421 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2422 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002423 }
2424 mShortcutsAndWidgets.requestLayout();
2425 }
2426 return swapSolution.isSolution;
2427 }
2428
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002429 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002430 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002431 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002432 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002433
2434 if (resultSpan == null) {
2435 resultSpan = new int[2];
2436 }
2437
Adam Cohen19f37922012-03-21 11:59:11 -07002438 // When we are checking drop validity or actually dropping, we don't recompute the
2439 // direction vector, since we want the solution to match the preview, and it's possible
2440 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002441 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2442 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002443 mDirectionVector[0] = mPreviousReorderDirection[0];
2444 mDirectionVector[1] = mPreviousReorderDirection[1];
2445 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002446 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2447 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2448 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002449 }
2450 } else {
2451 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2452 mPreviousReorderDirection[0] = mDirectionVector[0];
2453 mPreviousReorderDirection[1] = mDirectionVector[1];
2454 }
2455
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002456 // Find a solution involving pushing / displacing any items in the way
2457 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002458 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2459
2460 // We attempt the approach which doesn't shuffle views at all
2461 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2462 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2463
2464 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002465
2466 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2467 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002468 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2469 finalSolution = swapSolution;
2470 } else if (noShuffleSolution.isSolution) {
2471 finalSolution = noShuffleSolution;
2472 }
2473
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002474 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002475 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002476 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2477 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002478 result[0] = finalSolution.dragViewX;
2479 result[1] = finalSolution.dragViewY;
2480 resultSpan[0] = finalSolution.dragViewSpanX;
2481 resultSpan[1] = finalSolution.dragViewSpanY;
2482 } else {
2483 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2484 }
2485 return result;
2486 }
2487
Adam Cohen482ed822012-03-02 14:15:13 -08002488 boolean foundSolution = true;
2489 if (!DESTRUCTIVE_REORDER) {
2490 setUseTempCoords(true);
2491 }
2492
2493 if (finalSolution != null) {
2494 result[0] = finalSolution.dragViewX;
2495 result[1] = finalSolution.dragViewY;
2496 resultSpan[0] = finalSolution.dragViewSpanX;
2497 resultSpan[1] = finalSolution.dragViewSpanY;
2498
2499 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2500 // committing anything or animating anything as we just want to determine if a solution
2501 // exists
2502 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2503 if (!DESTRUCTIVE_REORDER) {
2504 copySolutionToTempState(finalSolution, dragView);
2505 }
2506 setItemPlacementDirty(true);
2507 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2508
Adam Cohen19f37922012-03-21 11:59:11 -07002509 if (!DESTRUCTIVE_REORDER &&
2510 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002511 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002512 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002513 setItemPlacementDirty(false);
2514 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002515 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2516 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002517 }
2518 }
2519 } else {
2520 foundSolution = false;
2521 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2522 }
2523
2524 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2525 setUseTempCoords(false);
2526 }
Adam Cohen482ed822012-03-02 14:15:13 -08002527
Michael Jurkaa52570f2012-03-20 03:18:20 -07002528 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002529 return result;
2530 }
2531
Adam Cohen19f37922012-03-21 11:59:11 -07002532 void setItemPlacementDirty(boolean dirty) {
2533 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002534 }
Adam Cohen19f37922012-03-21 11:59:11 -07002535 boolean isItemPlacementDirty() {
2536 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002537 }
2538
Adam Cohen091440a2015-03-18 14:16:05 -07002539 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002540 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002541 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2542 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002543 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002544 boolean isSolution = false;
2545 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2546
Adam Cohenf3900c22012-11-16 18:28:11 -08002547 void save() {
2548 // Copy current state into savedMap
2549 for (View v: map.keySet()) {
2550 map.get(v).copy(savedMap.get(v));
2551 }
2552 }
2553
2554 void restore() {
2555 // Restore current state from savedMap
2556 for (View v: savedMap.keySet()) {
2557 savedMap.get(v).copy(map.get(v));
2558 }
2559 }
2560
2561 void add(View v, CellAndSpan cs) {
2562 map.put(v, cs);
2563 savedMap.put(v, new CellAndSpan());
2564 sortedViews.add(v);
2565 }
2566
Adam Cohen482ed822012-03-02 14:15:13 -08002567 int area() {
2568 return dragViewSpanX * dragViewSpanY;
2569 }
Adam Cohen8baab352012-03-20 17:39:21 -07002570 }
2571
2572 private class CellAndSpan {
2573 int x, y;
2574 int spanX, spanY;
2575
Adam Cohenf3900c22012-11-16 18:28:11 -08002576 public CellAndSpan() {
2577 }
2578
2579 public void copy(CellAndSpan copy) {
2580 copy.x = x;
2581 copy.y = y;
2582 copy.spanX = spanX;
2583 copy.spanY = spanY;
2584 }
2585
Adam Cohen8baab352012-03-20 17:39:21 -07002586 public CellAndSpan(int x, int y, int spanX, int spanY) {
2587 this.x = x;
2588 this.y = y;
2589 this.spanX = spanX;
2590 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002591 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002592
2593 public String toString() {
2594 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2595 }
2596
Adam Cohen482ed822012-03-02 14:15:13 -08002597 }
2598
Adam Cohendf035382011-04-11 17:22:04 -07002599 /**
Adam Cohendf035382011-04-11 17:22:04 -07002600 * Find a starting cell position that will fit the given bounds nearest the requested
2601 * cell location. Uses Euclidean distance to score multiple vacant areas.
2602 *
2603 * @param pixelX The X location at which you want to search for a vacant area.
2604 * @param pixelY The Y location at which you want to search for a vacant area.
2605 * @param spanX Horizontal span of the object.
2606 * @param spanY Vertical span of the object.
2607 * @param ignoreView Considers space occupied by this view as unoccupied
2608 * @param result Previously returned value to possibly recycle.
2609 * @return The X, Y cell of a vacant area that can contain this object,
2610 * nearest the requested location.
2611 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002612 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2613 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002614 }
2615
Michael Jurka0280c3b2010-09-17 15:00:07 -07002616 boolean existsEmptyCell() {
2617 return findCellForSpan(null, 1, 1);
2618 }
2619
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002620 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002621 * Finds the upper-left coordinate of the first rectangle in the grid that can
2622 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2623 * then this method will only return coordinates for rectangles that contain the cell
2624 * (intersectX, intersectY)
2625 *
2626 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2627 * can be found.
2628 * @param spanX The horizontal span of the cell we want to find.
2629 * @param spanY The vertical span of the cell we want to find.
2630 *
2631 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002632 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002633 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002634 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002635 final int endX = mCountX - (spanX - 1);
2636 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002637
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002638 for (int y = 0; y < endY && !foundCell; y++) {
2639 inner:
2640 for (int x = 0; x < endX; x++) {
2641 for (int i = 0; i < spanX; i++) {
2642 for (int j = 0; j < spanY; j++) {
2643 if (mOccupied[x + i][y + j]) {
2644 // small optimization: we can skip to after the column we just found
2645 // an occupied cell
2646 x += i;
2647 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002648 }
2649 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002650 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002651 if (cellXY != null) {
2652 cellXY[0] = x;
2653 cellXY[1] = y;
2654 }
2655 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002656 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002657 }
2658 }
2659
Michael Jurka28750fb2010-09-24 17:43:49 -07002660 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002661 }
2662
2663 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002664 * A drag event has begun over this layout.
2665 * It may have begun over this layout (in which case onDragChild is called first),
2666 * or it may have begun on another layout.
2667 */
2668 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002669 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002670 mDragging = true;
2671 }
2672
2673 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002674 * Called when drag has left this CellLayout or has been completed (successfully or not)
2675 */
2676 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002677 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002678 // This can actually be called when we aren't in a drag, e.g. when adding a new
2679 // item to this layout via the customize drawer.
2680 // Guard against that case.
2681 if (mDragging) {
2682 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002683 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002684
2685 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002686 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002687 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2688 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002689 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002690 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002691 }
2692
2693 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002694 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002695 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002696 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002697 *
2698 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002700 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002701 if (child != null) {
2702 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002703 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002704 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002705 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002706 }
2707
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002709 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002710 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002711 * @param cellX X coordinate of upper left corner expressed as a cell position
2712 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002713 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002714 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002715 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002716 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002717 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002718 final int cellWidth = mCellWidth;
2719 final int cellHeight = mCellHeight;
2720 final int widthGap = mWidthGap;
2721 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002722
Winson Chung4b825dcd2011-06-19 12:41:22 -07002723 final int hStartPadding = getPaddingLeft();
2724 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002725
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002726 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2727 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2728
2729 int x = hStartPadding + cellX * (cellWidth + widthGap);
2730 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002731
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002732 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002733 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002734
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002735 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002736 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002737 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002738 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002739 * @param width Width in pixels
2740 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002741 * @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 -08002742 */
Winson Chung66700732013-08-20 16:56:15 -07002743 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002744 LauncherAppState app = LauncherAppState.getInstance();
2745 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002746 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2747 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002748
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002749 // Always assume we're working with the smallest span to make sure we
2750 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002751 int parentWidth = grid.calculateCellWidth(grid.widthPx
2752 - padding.left - padding.right, (int) grid.numColumns);
2753 int parentHeight = grid.calculateCellHeight(grid.heightPx
2754 - padding.top - padding.bottom, (int) grid.numRows);
2755 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002756
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002757 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002758 int spanX = (int) Math.ceil(width / (float) smallerSize);
2759 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002760
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002761 if (result == null) {
2762 return new int[] { spanX, spanY };
2763 }
2764 result[0] = spanX;
2765 result[1] = spanY;
2766 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002767 }
2768
2769 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002770 * Calculate the grid spans needed to fit given item
2771 */
2772 public void calculateSpans(ItemInfo info) {
2773 final int minWidth;
2774 final int minHeight;
2775
2776 if (info instanceof LauncherAppWidgetInfo) {
2777 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2778 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2779 } else if (info instanceof PendingAddWidgetInfo) {
2780 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2781 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2782 } else {
2783 // It's not a widget, so it must be 1x1
2784 info.spanX = info.spanY = 1;
2785 return;
2786 }
2787 int[] spans = rectToCell(minWidth, minHeight, null);
2788 info.spanX = spans[0];
2789 info.spanY = spans[1];
2790 }
2791
Michael Jurka0280c3b2010-09-17 15:00:07 -07002792 private void clearOccupiedCells() {
2793 for (int x = 0; x < mCountX; x++) {
2794 for (int y = 0; y < mCountY; y++) {
2795 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002796 }
2797 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002798 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002799
Adam Cohend4844c32011-02-18 19:25:06 -08002800 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002801 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002802 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002803 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002804 }
2805
Adam Cohend4844c32011-02-18 19:25:06 -08002806 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002807 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002808 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002809 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002810 }
2811
Adam Cohen482ed822012-03-02 14:15:13 -08002812 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2813 boolean value) {
2814 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002815 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2816 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002817 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002818 }
2819 }
2820 }
2821
Adam Cohen2801caf2011-05-13 20:57:39 -07002822 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002823 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002824 (Math.max((mCountX - 1), 0) * mWidthGap);
2825 }
2826
2827 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002828 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002829 (Math.max((mCountY - 1), 0) * mHeightGap);
2830 }
2831
Michael Jurka66d72172011-04-12 16:29:25 -07002832 public boolean isOccupied(int x, int y) {
2833 if (x < mCountX && y < mCountY) {
2834 return mOccupied[x][y];
2835 } else {
2836 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2837 }
2838 }
2839
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002840 @Override
2841 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2842 return new CellLayout.LayoutParams(getContext(), attrs);
2843 }
2844
2845 @Override
2846 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2847 return p instanceof CellLayout.LayoutParams;
2848 }
2849
2850 @Override
2851 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2852 return new CellLayout.LayoutParams(p);
2853 }
2854
2855 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2856 /**
2857 * Horizontal location of the item in the grid.
2858 */
2859 @ViewDebug.ExportedProperty
2860 public int cellX;
2861
2862 /**
2863 * Vertical location of the item in the grid.
2864 */
2865 @ViewDebug.ExportedProperty
2866 public int cellY;
2867
2868 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002869 * Temporary horizontal location of the item in the grid during reorder
2870 */
2871 public int tmpCellX;
2872
2873 /**
2874 * Temporary vertical location of the item in the grid during reorder
2875 */
2876 public int tmpCellY;
2877
2878 /**
2879 * Indicates that the temporary coordinates should be used to layout the items
2880 */
2881 public boolean useTmpCoords;
2882
2883 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002884 * Number of cells spanned horizontally by the item.
2885 */
2886 @ViewDebug.ExportedProperty
2887 public int cellHSpan;
2888
2889 /**
2890 * Number of cells spanned vertically by the item.
2891 */
2892 @ViewDebug.ExportedProperty
2893 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002894
Adam Cohen1b607ed2011-03-03 17:26:50 -08002895 /**
2896 * Indicates whether the item will set its x, y, width and height parameters freely,
2897 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2898 */
Adam Cohend4844c32011-02-18 19:25:06 -08002899 public boolean isLockedToGrid = true;
2900
Adam Cohen482ed822012-03-02 14:15:13 -08002901 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002902 * Indicates that this item should use the full extents of its parent.
2903 */
2904 public boolean isFullscreen = false;
2905
2906 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002907 * Indicates whether this item can be reordered. Always true except in the case of the
2908 * the AllApps button.
2909 */
2910 public boolean canReorder = true;
2911
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002912 // X coordinate of the view in the layout.
2913 @ViewDebug.ExportedProperty
2914 int x;
2915 // Y coordinate of the view in the layout.
2916 @ViewDebug.ExportedProperty
2917 int y;
2918
Romain Guy84f296c2009-11-04 15:00:44 -08002919 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002920
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002921 public LayoutParams(Context c, AttributeSet attrs) {
2922 super(c, attrs);
2923 cellHSpan = 1;
2924 cellVSpan = 1;
2925 }
2926
2927 public LayoutParams(ViewGroup.LayoutParams source) {
2928 super(source);
2929 cellHSpan = 1;
2930 cellVSpan = 1;
2931 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002932
2933 public LayoutParams(LayoutParams source) {
2934 super(source);
2935 this.cellX = source.cellX;
2936 this.cellY = source.cellY;
2937 this.cellHSpan = source.cellHSpan;
2938 this.cellVSpan = source.cellVSpan;
2939 }
2940
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002941 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002942 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002943 this.cellX = cellX;
2944 this.cellY = cellY;
2945 this.cellHSpan = cellHSpan;
2946 this.cellVSpan = cellVSpan;
2947 }
2948
Adam Cohen2374abf2013-04-16 14:56:57 -07002949 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2950 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002951 if (isLockedToGrid) {
2952 final int myCellHSpan = cellHSpan;
2953 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002954 int myCellX = useTmpCoords ? tmpCellX : cellX;
2955 int myCellY = useTmpCoords ? tmpCellY : cellY;
2956
2957 if (invertHorizontally) {
2958 myCellX = colCount - myCellX - cellHSpan;
2959 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002960
Adam Cohend4844c32011-02-18 19:25:06 -08002961 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2962 leftMargin - rightMargin;
2963 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2964 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002965 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2966 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002967 }
2968 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002969
Winson Chungaafa03c2010-06-11 17:34:16 -07002970 public String toString() {
2971 return "(" + this.cellX + ", " + this.cellY + ")";
2972 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002973
2974 public void setWidth(int width) {
2975 this.width = width;
2976 }
2977
2978 public int getWidth() {
2979 return width;
2980 }
2981
2982 public void setHeight(int height) {
2983 this.height = height;
2984 }
2985
2986 public int getHeight() {
2987 return height;
2988 }
2989
2990 public void setX(int x) {
2991 this.x = x;
2992 }
2993
2994 public int getX() {
2995 return x;
2996 }
2997
2998 public void setY(int y) {
2999 this.y = y;
3000 }
3001
3002 public int getY() {
3003 return y;
3004 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003005 }
3006
Michael Jurka0280c3b2010-09-17 15:00:07 -07003007 // This class stores info for two purposes:
3008 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3009 // its spanX, spanY, and the screen it is on
3010 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3011 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3012 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003013 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003014 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003015 int cellX = -1;
3016 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003017 int spanX;
3018 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003019 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003020 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003021
Adam Cohene0aaa0d2014-05-12 12:44:22 -07003022 CellInfo(View v, ItemInfo info) {
3023 cell = v;
3024 cellX = info.cellX;
3025 cellY = info.cellY;
3026 spanX = info.spanX;
3027 spanY = info.spanY;
3028 screenId = info.screenId;
3029 container = info.container;
3030 }
3031
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003032 @Override
3033 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003034 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3035 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003036 }
3037 }
Michael Jurkad771c962011-08-09 15:00:48 -07003038
3039 public boolean lastDownOnOccupiedCell() {
3040 return mLastDownOnOccupiedCell;
3041 }
Sunny Goyala9116722015-04-29 13:55:58 -07003042
3043 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
3044 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
3045 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003046
3047 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3048 int x2 = x + spanX - 1;
3049 int y2 = y + spanY - 1;
3050 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3051 return false;
3052 }
3053 for (int i = x; i <= x2; i++) {
3054 for (int j = y; j <= y2; j++) {
3055 if (mOccupied[i][j]) {
3056 return false;
3057 }
3058 }
3059 }
3060
3061 return true;
3062 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003063}