blob: 2cde3d53dc0defdc4483a4759f7015a5a79ead90 [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;
Sunny Goyal2805e632015-05-20 15:35:32 -070037import android.graphics.drawable.TransitionDrawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080038import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080040import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070042import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070043import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080044import android.view.MotionEvent;
45import android.view.View;
46import android.view.ViewDebug;
47import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080048import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070049import android.view.animation.DecelerateInterpolator;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080050
Sunny Goyal4b6eb262015-05-14 19:24:40 -070051import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Daniel Sandler325dc232013-06-05 22:57:57 -040052import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyale9b651e2015-04-24 11:44:51 -070053import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
54import com.android.launcher3.accessibility.FolderAccessibilityHelper;
55import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Adam Cohen091440a2015-03-18 14:16:05 -070056import com.android.launcher3.util.Thunk;
Hyunyoung Song3f471442015-04-08 19:01:34 -070057import com.android.launcher3.widget.PendingAddWidgetInfo;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070058
Adam Cohen69ce2e52011-07-03 19:25:21 -070059import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070060import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080061import java.util.Collections;
62import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070063import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080064import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070065
Sunny Goyal4b6eb262015-05-14 19:24:40 -070066public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070067 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
68 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
69
Winson Chungaafa03c2010-06-11 17:34:16 -070070 static final String TAG = "CellLayout";
71
Adam Cohen2acce882012-03-28 19:03:19 -070072 private Launcher mLauncher;
Adam Cohen091440a2015-03-18 14:16:05 -070073 @Thunk int mCellWidth;
74 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070075 private int mFixedCellWidth;
76 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070077
Adam Cohen091440a2015-03-18 14:16:05 -070078 @Thunk int mCountX;
79 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080080
Adam Cohen234c4cd2011-07-17 21:03:04 -070081 private int mOriginalWidthGap;
82 private int mOriginalHeightGap;
Adam Cohen091440a2015-03-18 14:16:05 -070083 @Thunk int mWidthGap;
84 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070085 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070086 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070087 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088
Patrick Dubroyde7658b2010-09-27 11:15:43 -070089 // These are temporary variables to prevent having to allocate a new object just to
90 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -070091 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -070092 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070093
The Android Open Source Project31dd5032009-03-03 19:32:27 -080094 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080095 boolean[][] mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080096
Michael Jurkadee05892010-07-27 10:01:56 -070097 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -070098 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -070099
Adam Cohen69ce2e52011-07-03 19:25:21 -0700100 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700101 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700102
Michael Jurka5f1c5092010-09-03 14:15:02 -0700103 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700104
Sunny Goyal2805e632015-05-20 15:35:32 -0700105 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
106 private final TransitionDrawable mBackground;
107
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700108 // These values allow a fixed measurement to be set on the CellLayout.
109 private int mFixedWidth = -1;
110 private int mFixedHeight = -1;
111
Michael Jurka33945b22010-12-21 18:19:38 -0800112 // If we're actively dragging something over this screen, mIsDragOverlapping is true
113 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700114
Winson Chung150fbab2010-09-29 17:14:26 -0700115 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700116 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700117 @Thunk Rect[] mDragOutlines = new Rect[4];
118 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700119 private InterruptibleInOutAnimator[] mDragOutlineAnims =
120 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700121
122 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700123 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700124 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700125
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700126 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800127
Sunny Goyal316490e2015-06-02 09:38:28 -0700128 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
129 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700130
131 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700132
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700133 // When a drag operation is in progress, holds the nearest cell to the touch point
134 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800135
Joe Onorato4be866d2010-10-10 11:26:02 -0700136 private boolean mDragging = false;
137
Patrick Dubroyce34a972010-10-19 10:34:32 -0700138 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700139 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700140
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800141 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700142 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800143
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800144 public static final int MODE_SHOW_REORDER_HINT = 0;
145 public static final int MODE_DRAG_OVER = 1;
146 public static final int MODE_ON_DROP = 2;
147 public static final int MODE_ON_DROP_EXTERNAL = 3;
148 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700149 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800150 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
151
Adam Cohena897f392012-04-27 18:12:05 -0700152 static final int LANDSCAPE = 0;
153 static final int PORTRAIT = 1;
154
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800155 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700156 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700157 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700158
Adam Cohen482ed822012-03-02 14:15:13 -0800159 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
160 private Rect mOccupiedRect = new Rect();
161 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700162 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700163 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800164
Sunny Goyal2805e632015-05-20 15:35:32 -0700165 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700166
Michael Jurkaca993832012-06-29 15:17:04 -0700167 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700168
Adam Cohenc9735cf2015-01-23 16:11:55 -0800169 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700170 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800171 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800172
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800173 public CellLayout(Context context) {
174 this(context, null);
175 }
176
177 public CellLayout(Context context, AttributeSet attrs) {
178 this(context, attrs, 0);
179 }
180
181 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
182 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700183
184 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
185 // the user where a dragged item will land when dropped.
186 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800187 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700188 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700189
Adam Cohen2e6da152015-05-06 11:42:25 -0700190 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800191 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
192
Winson Chung11a1a532013-09-13 11:14:45 -0700193 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800194 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700195 mWidthGap = mOriginalWidthGap = 0;
196 mHeightGap = mOriginalHeightGap = 0;
197 mMaxGap = Integer.MAX_VALUE;
Adam Cohen2e6da152015-05-06 11:42:25 -0700198 mCountX = (int) grid.inv.numColumns;
199 mCountY = (int) grid.inv.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700200 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800201 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700202 mPreviousReorderDirection[0] = INVALID_DIRECTION;
203 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800204
205 a.recycle();
206
207 setAlwaysDrawnWithCacheEnabled(false);
208
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700209 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700210 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700211
Sunny Goyal2805e632015-05-20 15:35:32 -0700212 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
213 mBackground.setCallback(this);
Michael Jurka33945b22010-12-21 18:19:38 -0800214
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800215 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700216 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700217
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700218 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700219 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700220 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700221 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800222 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700223 }
224
225 // When dragging things around the home screens, we show a green outline of
226 // where the item will land. The outlines gradually fade out, leaving a trail
227 // behind the drag path.
228 // Set up all the animations that are used to implement this fading.
229 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700230 final float fromAlphaValue = 0;
231 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700232
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700233 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700234
235 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700236 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100237 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700238 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700239 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700240 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700241 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700242 final Bitmap outline = (Bitmap)anim.getTag();
243
244 // If an animation is started and then stopped very quickly, we can still
245 // get spurious updates we've cleared the tag. Guard against this.
246 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700247 @SuppressWarnings("all") // suppress dead code warning
248 final boolean debug = false;
249 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700250 Object val = animation.getAnimatedValue();
251 Log.d(TAG, "anim " + thisIndex + " update: " + val +
252 ", isStopped " + anim.isStopped());
253 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700254 // Try to prevent it from continuing to run
255 animation.cancel();
256 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700257 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800258 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700259 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700260 }
261 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700262 // The animation holds a reference to the drag outline bitmap as long is it's
263 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700264 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700265 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700266 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700267 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700268 anim.setTag(null);
269 }
270 }
271 });
272 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700273 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700274
Michael Jurkaa52570f2012-03-20 03:18:20 -0700275 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700276 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700277 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700278
Mady Melloref044dd2015-06-02 15:35:07 -0700279 mStylusEventHelper = new StylusEventHelper(this);
280
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700281 mTouchFeedbackView = new ClickShadowView(context);
282 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700283 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700284 }
285
Adam Cohenc9735cf2015-01-23 16:11:55 -0800286 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700287 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800288 mUseTouchHelper = enable;
289 if (!enable) {
290 ViewCompat.setAccessibilityDelegate(this, null);
291 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
292 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
293 setOnClickListener(mLauncher);
294 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700295 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
296 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
297 mTouchHelper = new WorkspaceAccessibilityHelper(this);
298 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
299 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
300 mTouchHelper = new FolderAccessibilityHelper(this);
301 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800302 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
303 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
304 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
305 setOnClickListener(mTouchHelper);
306 }
307
308 // Invalidate the accessibility hierarchy
309 if (getParent() != null) {
310 getParent().notifySubtreeAccessibilityStateChanged(
311 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
312 }
313 }
314
315 @Override
316 public boolean dispatchHoverEvent(MotionEvent event) {
317 // Always attempt to dispatch hover events to accessibility first.
318 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
319 return true;
320 }
321 return super.dispatchHoverEvent(event);
322 }
323
324 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800325 public boolean onInterceptTouchEvent(MotionEvent ev) {
326 if (mUseTouchHelper ||
327 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
328 return true;
329 }
330 return false;
331 }
332
Mady Melloref044dd2015-06-02 15:35:07 -0700333 @Override
334 public boolean onTouchEvent(MotionEvent ev) {
335 boolean handled = super.onTouchEvent(ev);
336 // Stylus button press on a home screen should not switch between overview mode and
337 // the home screen mode, however, once in overview mode stylus button press should be
338 // enabled to allow rearranging the different home screens. So check what mode
339 // the workspace is in, and only perform stylus button presses while in overview mode.
340 if (mLauncher.mWorkspace.isInOverviewMode()
341 && mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
342 return true;
343 }
344 return handled;
345 }
346
Chris Craik01f2d7f2013-10-01 14:41:56 -0700347 public void enableHardwareLayer(boolean hasLayer) {
348 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700349 }
350
351 public void buildHardwareLayer() {
352 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700353 }
354
Adam Cohen307fe232012-08-16 17:55:58 -0700355 public float getChildrenScale() {
356 return mIsHotseat ? mHotseatScale : 1.0f;
357 }
358
Winson Chung5f8afe62013-08-12 16:19:28 -0700359 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700360 mFixedCellWidth = mCellWidth = width;
361 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700362 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
363 mCountX, mCountY);
364 }
365
Adam Cohen2801caf2011-05-13 20:57:39 -0700366 public void setGridSize(int x, int y) {
367 mCountX = x;
368 mCountY = y;
369 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800370 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700371 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700372 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700373 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700374 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700375 }
376
Adam Cohen2374abf2013-04-16 14:56:57 -0700377 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
378 public void setInvertIfRtl(boolean invert) {
379 mShortcutsAndWidgets.setInvertIfRtl(invert);
380 }
381
Adam Cohen917e3882013-10-31 15:03:35 -0700382 public void setDropPending(boolean pending) {
383 mDropPending = pending;
384 }
385
386 public boolean isDropPending() {
387 return mDropPending;
388 }
389
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700390 @Override
391 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700392 if (icon == null || background == null) {
393 mTouchFeedbackView.setBitmap(null);
394 mTouchFeedbackView.animate().cancel();
395 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700396 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700397 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
398 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700399 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800400 }
401 }
402
Adam Cohenc50438c2014-08-19 17:43:05 -0700403 void disableDragTarget() {
404 mIsDragTarget = false;
405 }
406
407 boolean isDragTarget() {
408 return mIsDragTarget;
409 }
410
411 void setIsDragOverlapping(boolean isDragOverlapping) {
412 if (mIsDragOverlapping != isDragOverlapping) {
413 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700414 if (mIsDragOverlapping) {
415 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
416 } else {
417 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
418 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700419 invalidate();
420 }
421 }
422
Michael Jurka33945b22010-12-21 18:19:38 -0800423 boolean getIsDragOverlapping() {
424 return mIsDragOverlapping;
425 }
426
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700427 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700428 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700429 if (!mIsDragTarget) {
430 return;
431 }
432
Michael Jurka3e7c7632010-10-02 16:01:03 -0700433 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
434 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
435 // When we're small, we are either drawn normally or in the "accepts drops" state (during
436 // a drag). However, we also drag the mini hover background *over* one of those two
437 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700438 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700439 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700440 }
Romain Guya6abce82009-11-10 02:54:41 -0800441
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700442 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700443 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700444 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700445 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800446 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700447 mTempRect.set(r);
448 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700449 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700450 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700451 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700452 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700453 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800454
Adam Cohen482ed822012-03-02 14:15:13 -0800455 if (DEBUG_VISUALIZE_OCCUPIED) {
456 int[] pt = new int[2];
457 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700458 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800459 for (int i = 0; i < mCountX; i++) {
460 for (int j = 0; j < mCountY; j++) {
461 if (mOccupied[i][j]) {
462 cellToPoint(i, j, pt);
463 canvas.save();
464 canvas.translate(pt[0], pt[1]);
465 cd.draw(canvas);
466 canvas.restore();
467 }
468 }
469 }
470 }
471
Andrew Flynn850d2e72012-04-26 16:51:20 -0700472 int previewOffset = FolderRingAnimator.sPreviewSize;
473
Adam Cohen69ce2e52011-07-03 19:25:21 -0700474 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700475 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700476 for (int i = 0; i < mFolderOuterRings.size(); i++) {
477 FolderRingAnimator fra = mFolderOuterRings.get(i);
478
Adam Cohen5108bc02013-09-20 17:04:51 -0700479 Drawable d;
480 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700481 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700482 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700483
Winson Chung89f97052013-09-20 11:32:26 -0700484 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700485 int centerX = mTempLocation[0] + mCellWidth / 2;
486 int centerY = mTempLocation[1] + previewOffset / 2 +
487 child.getPaddingTop() + grid.folderBackgroundOffset;
488
Adam Cohen5108bc02013-09-20 17:04:51 -0700489 // Draw outer ring, if it exists
490 if (FolderIcon.HAS_OUTER_RING) {
491 d = FolderRingAnimator.sSharedOuterRingDrawable;
492 width = (int) (fra.getOuterRingSize() * getChildrenScale());
493 height = width;
494 canvas.save();
495 canvas.translate(centerX - width / 2, centerY - height / 2);
496 d.setBounds(0, 0, width, height);
497 d.draw(canvas);
498 canvas.restore();
499 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700500
Winson Chung89f97052013-09-20 11:32:26 -0700501 // Draw inner ring
502 d = FolderRingAnimator.sSharedInnerRingDrawable;
503 width = (int) (fra.getInnerRingSize() * getChildrenScale());
504 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700505 canvas.save();
506 canvas.translate(centerX - width / 2, centerY - width / 2);
507 d.setBounds(0, 0, width, height);
508 d.draw(canvas);
509 canvas.restore();
510 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700511 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700512
513 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
514 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
515 int width = d.getIntrinsicWidth();
516 int height = d.getIntrinsicHeight();
517
518 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700519 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700520 if (child != null) {
521 int centerX = mTempLocation[0] + mCellWidth / 2;
522 int centerY = mTempLocation[1] + previewOffset / 2 +
523 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700524
Winson Chung89f97052013-09-20 11:32:26 -0700525 canvas.save();
526 canvas.translate(centerX - width / 2, centerY - width / 2);
527 d.setBounds(0, 0, width, height);
528 d.draw(canvas);
529 canvas.restore();
530 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700531 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700532 }
533
534 public void showFolderAccept(FolderRingAnimator fra) {
535 mFolderOuterRings.add(fra);
536 }
537
538 public void hideFolderAccept(FolderRingAnimator fra) {
539 if (mFolderOuterRings.contains(fra)) {
540 mFolderOuterRings.remove(fra);
541 }
542 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700543 }
544
Adam Cohenc51934b2011-07-26 21:07:43 -0700545 public void setFolderLeaveBehindCell(int x, int y) {
546 mFolderLeaveBehindCell[0] = x;
547 mFolderLeaveBehindCell[1] = y;
548 invalidate();
549 }
550
551 public void clearFolderLeaveBehind() {
552 mFolderLeaveBehindCell[0] = -1;
553 mFolderLeaveBehindCell[1] = -1;
554 invalidate();
555 }
556
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700557 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700558 public boolean shouldDelayChildPressedState() {
559 return false;
560 }
561
Adam Cohen1462de32012-07-24 22:34:36 -0700562 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700563 try {
564 dispatchRestoreInstanceState(states);
565 } catch (IllegalArgumentException ex) {
566 if (LauncherAppState.isDogfoodBuild()) {
567 throw ex;
568 }
569 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
570 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
571 }
Adam Cohen1462de32012-07-24 22:34:36 -0700572 }
573
Michael Jurkae6235dd2011-10-04 15:02:05 -0700574 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700575 public void cancelLongPress() {
576 super.cancelLongPress();
577
578 // Cancel long press for all children
579 final int count = getChildCount();
580 for (int i = 0; i < count; i++) {
581 final View child = getChildAt(i);
582 child.cancelLongPress();
583 }
584 }
585
Michael Jurkadee05892010-07-27 10:01:56 -0700586 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
587 mInterceptTouchListener = listener;
588 }
589
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800590 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700591 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800592 }
593
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800594 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700595 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800596 }
597
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800598 public void setIsHotseat(boolean isHotseat) {
599 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700600 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800601 }
602
Sunny Goyale9b651e2015-04-24 11:44:51 -0700603 public boolean isHotseat() {
604 return mIsHotseat;
605 }
606
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800607 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700608 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700609 final LayoutParams lp = params;
610
Andrew Flynnde38e422012-05-08 11:22:15 -0700611 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800612 if (child instanceof BubbleTextView) {
613 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700614 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800615 }
616
Adam Cohen307fe232012-08-16 17:55:58 -0700617 child.setScaleX(getChildrenScale());
618 child.setScaleY(getChildrenScale());
619
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800620 // Generate an id for each view, this assumes we have at most 256x256 cells
621 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700622 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700623 // If the horizontal or vertical span is set to -1, it is taken to
624 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700625 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
626 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800627
Winson Chungaafa03c2010-06-11 17:34:16 -0700628 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700629 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700630
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700631 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700632
Winson Chungaafa03c2010-06-11 17:34:16 -0700633 return true;
634 }
635 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800636 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700637
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800638 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700639 public void removeAllViews() {
640 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700641 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700642 }
643
644 @Override
645 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700646 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700647 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700648 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700649 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700650 }
651
652 @Override
653 public void removeView(View view) {
654 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700655 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700656 }
657
658 @Override
659 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700660 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
661 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700662 }
663
664 @Override
665 public void removeViewInLayout(View view) {
666 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700668 }
669
670 @Override
671 public void removeViews(int start, int count) {
672 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700673 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700674 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700675 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700676 }
677
678 @Override
679 public void removeViewsInLayout(int start, int count) {
680 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700681 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700682 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800684 }
685
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700686 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700687 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800688 * @param x X coordinate of the point
689 * @param y Y coordinate of the point
690 * @param result Array of 2 ints to hold the x and y coordinate of the cell
691 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700692 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700693 final int hStartPadding = getPaddingLeft();
694 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800695
696 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
697 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
698
Adam Cohend22015c2010-07-26 22:02:18 -0700699 final int xAxis = mCountX;
700 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800701
702 if (result[0] < 0) result[0] = 0;
703 if (result[0] >= xAxis) result[0] = xAxis - 1;
704 if (result[1] < 0) result[1] = 0;
705 if (result[1] >= yAxis) result[1] = yAxis - 1;
706 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700707
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800708 /**
709 * Given a point, return the cell that most closely encloses that point
710 * @param x X coordinate of the point
711 * @param y Y coordinate of the point
712 * @param result Array of 2 ints to hold the x and y coordinate of the cell
713 */
714 void pointToCellRounded(int x, int y, int[] result) {
715 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
716 }
717
718 /**
719 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700720 *
721 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800722 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700723 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800724 * @param result Array of 2 ints to hold the x and y coordinate of the point
725 */
726 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700727 final int hStartPadding = getPaddingLeft();
728 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800729
730 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
731 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
732 }
733
Adam Cohene3e27a82011-04-15 12:07:39 -0700734 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800735 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700736 *
737 * @param cellX X coordinate of the cell
738 * @param cellY Y coordinate of the cell
739 *
740 * @param result Array of 2 ints to hold the x and y coordinate of the point
741 */
742 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700743 regionToCenterPoint(cellX, cellY, 1, 1, result);
744 }
745
746 /**
747 * Given a cell coordinate and span return the point that represents the center of the regio
748 *
749 * @param cellX X coordinate of the cell
750 * @param cellY Y coordinate of the cell
751 *
752 * @param result Array of 2 ints to hold the x and y coordinate of the point
753 */
754 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700755 final int hStartPadding = getPaddingLeft();
756 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700757 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
758 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
759 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
760 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700761 }
762
Adam Cohen19f37922012-03-21 11:59:11 -0700763 /**
764 * Given a cell coordinate and span fills out a corresponding pixel rect
765 *
766 * @param cellX X coordinate of the cell
767 * @param cellY Y coordinate of the cell
768 * @param result Rect in which to write the result
769 */
770 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
771 final int hStartPadding = getPaddingLeft();
772 final int vStartPadding = getPaddingTop();
773 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
774 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
775 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
776 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
777 }
778
Adam Cohen482ed822012-03-02 14:15:13 -0800779 public float getDistanceFromCell(float x, float y, int[] cell) {
780 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700781 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800782 }
783
Romain Guy84f296c2009-11-04 15:00:44 -0800784 int getCellWidth() {
785 return mCellWidth;
786 }
787
788 int getCellHeight() {
789 return mCellHeight;
790 }
791
Adam Cohend4844c32011-02-18 19:25:06 -0800792 int getWidthGap() {
793 return mWidthGap;
794 }
795
796 int getHeightGap() {
797 return mHeightGap;
798 }
799
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700800 public void setFixedSize(int width, int height) {
801 mFixedWidth = width;
802 mFixedHeight = height;
803 }
804
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800805 @Override
806 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800807 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800808 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700809 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
810 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700811 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
812 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700813 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700814 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
815 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700816 if (cw != mCellWidth || ch != mCellHeight) {
817 mCellWidth = cw;
818 mCellHeight = ch;
819 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
820 mHeightGap, mCountX, mCountY);
821 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700822 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700823
Winson Chung2d75f122013-09-23 16:53:31 -0700824 int newWidth = childWidthSize;
825 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700826 if (mFixedWidth > 0 && mFixedHeight > 0) {
827 newWidth = mFixedWidth;
828 newHeight = mFixedHeight;
829 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800830 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
831 }
832
Adam Cohend22015c2010-07-26 22:02:18 -0700833 int numWidthGaps = mCountX - 1;
834 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800835
Adam Cohen234c4cd2011-07-17 21:03:04 -0700836 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700837 int hSpace = childWidthSize;
838 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700839 int hFreeSpace = hSpace - (mCountX * mCellWidth);
840 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700841 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
842 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700843 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
844 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700845 } else {
846 mWidthGap = mOriginalWidthGap;
847 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700848 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700849
850 // Make the feedback view large enough to hold the blur bitmap.
851 mTouchFeedbackView.measure(
852 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
853 MeasureSpec.EXACTLY),
854 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
855 MeasureSpec.EXACTLY));
856
857 mShortcutsAndWidgets.measure(
858 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
859 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
860
861 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
862 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700863 if (mFixedWidth > 0 && mFixedHeight > 0) {
864 setMeasuredDimension(maxWidth, maxHeight);
865 } else {
866 setMeasuredDimension(widthSize, heightSize);
867 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 }
869
870 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700871 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700872 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
873 (mCountX * mCellWidth);
874 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
875 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700876
877 mTouchFeedbackView.layout(left, top,
878 left + mTouchFeedbackView.getMeasuredWidth(),
879 top + mTouchFeedbackView.getMeasuredHeight());
880 mShortcutsAndWidgets.layout(left, top,
881 left + r - l,
882 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800883 }
884
885 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700886 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
887 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700888
889 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700890 mBackground.getPadding(mTempRect);
891 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
892 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700893 }
894
895 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800896 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700897 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800898 }
899
900 @Override
901 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700902 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800903 }
904
Michael Jurka5f1c5092010-09-03 14:15:02 -0700905 public float getBackgroundAlpha() {
906 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700907 }
908
Michael Jurka5f1c5092010-09-03 14:15:02 -0700909 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800910 if (mBackgroundAlpha != alpha) {
911 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700912 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800913 }
Michael Jurkadee05892010-07-27 10:01:56 -0700914 }
915
Sunny Goyal2805e632015-05-20 15:35:32 -0700916 @Override
917 protected boolean verifyDrawable(Drawable who) {
918 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
919 }
920
Michael Jurkaa52570f2012-03-20 03:18:20 -0700921 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700922 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700923 }
924
Michael Jurkaa52570f2012-03-20 03:18:20 -0700925 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700926 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700927 }
928
Patrick Dubroy440c3602010-07-13 17:50:32 -0700929 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700930 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700931 }
932
Adam Cohen76fc0852011-06-17 13:26:23 -0700933 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800934 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700935 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800936 boolean[][] occupied = mOccupied;
937 if (!permanent) {
938 occupied = mTmpOccupied;
939 }
940
Adam Cohen19f37922012-03-21 11:59:11 -0700941 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700942 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
943 final ItemInfo info = (ItemInfo) child.getTag();
944
945 // We cancel any existing animations
946 if (mReorderAnimators.containsKey(lp)) {
947 mReorderAnimators.get(lp).cancel();
948 mReorderAnimators.remove(lp);
949 }
950
Adam Cohen482ed822012-03-02 14:15:13 -0800951 final int oldX = lp.x;
952 final int oldY = lp.y;
953 if (adjustOccupied) {
954 occupied[lp.cellX][lp.cellY] = false;
955 occupied[cellX][cellY] = true;
956 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700957 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800958 if (permanent) {
959 lp.cellX = info.cellX = cellX;
960 lp.cellY = info.cellY = cellY;
961 } else {
962 lp.tmpCellX = cellX;
963 lp.tmpCellY = cellY;
964 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700965 clc.setupLp(lp);
966 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800967 final int newX = lp.x;
968 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700969
Adam Cohen76fc0852011-06-17 13:26:23 -0700970 lp.x = oldX;
971 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700972
Adam Cohen482ed822012-03-02 14:15:13 -0800973 // Exit early if we're not actually moving the view
974 if (oldX == newX && oldY == newY) {
975 lp.isLockedToGrid = true;
976 return true;
977 }
978
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100979 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800980 va.setDuration(duration);
981 mReorderAnimators.put(lp, va);
982
983 va.addUpdateListener(new AnimatorUpdateListener() {
984 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700985 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -0800986 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -0700987 lp.x = (int) ((1 - r) * oldX + r * newX);
988 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -0700989 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700990 }
991 });
Adam Cohen482ed822012-03-02 14:15:13 -0800992 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700993 boolean cancelled = false;
994 public void onAnimationEnd(Animator animation) {
995 // If the animation was cancelled, it means that another animation
996 // has interrupted this one, and we don't want to lock the item into
997 // place just yet.
998 if (!cancelled) {
999 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001000 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001001 }
1002 if (mReorderAnimators.containsKey(lp)) {
1003 mReorderAnimators.remove(lp);
1004 }
1005 }
1006 public void onAnimationCancel(Animator animation) {
1007 cancelled = true;
1008 }
1009 });
Adam Cohen482ed822012-03-02 14:15:13 -08001010 va.setStartDelay(delay);
1011 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001012 return true;
1013 }
1014 return false;
1015 }
1016
Adam Cohen482ed822012-03-02 14:15:13 -08001017 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1018 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001019 final int oldDragCellX = mDragCell[0];
1020 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001021
Adam Cohen2801caf2011-05-13 20:57:39 -07001022 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001023 return;
1024 }
1025
Adam Cohen482ed822012-03-02 14:15:13 -08001026 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1027 mDragCell[0] = cellX;
1028 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001029 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001030 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001031 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001032
Joe Onorato4be866d2010-10-10 11:26:02 -07001033 int left = topLeft[0];
1034 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001035
Winson Chungb8c69f32011-10-19 21:36:08 -07001036 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001037 // When drawing the drag outline, it did not account for margin offsets
1038 // added by the view's parent.
1039 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1040 left += lp.leftMargin;
1041 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001042
Adam Cohen99e8b402011-03-25 19:23:43 -07001043 // Offsets due to the size difference between the View and the dragOutline.
1044 // There is a size difference to account for the outer blur, which may lie
1045 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001046 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001047 // We center about the x axis
1048 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1049 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001050 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001051 if (dragOffset != null && dragRegion != null) {
1052 // Center the drag region *horizontally* in the cell and apply a drag
1053 // outline offset
1054 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1055 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001056 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1057 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1058 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001059 } else {
1060 // Center the drag outline in the cell
1061 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1062 - dragOutline.getWidth()) / 2;
1063 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1064 - dragOutline.getHeight()) / 2;
1065 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001066 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001067 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001068 mDragOutlineAnims[oldIndex].animateOut();
1069 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001070 Rect r = mDragOutlines[mDragOutlineCurrent];
1071 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1072 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001073 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001074 }
Winson Chung150fbab2010-09-29 17:14:26 -07001075
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001076 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1077 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001078 }
1079 }
1080
Adam Cohene0310962011-04-18 16:15:31 -07001081 public void clearDragOutlines() {
1082 final int oldIndex = mDragOutlineCurrent;
1083 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001084 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001085 }
1086
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001087 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001088 * Find a vacant area that will fit the given bounds nearest the requested
1089 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001090 *
Romain Guy51afc022009-05-04 18:03:43 -07001091 * @param pixelX The X location at which you want to search for a vacant area.
1092 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001093 * @param spanX Horizontal span of the object.
1094 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001095 * @param result Array in which to place the result, or null (in which case a new array will
1096 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001097 * @return The X, Y cell of a vacant area that can contain this object,
1098 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001099 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001100 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1101 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001102 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001103
Michael Jurka6a1435d2010-09-27 17:35:12 -07001104 /**
1105 * Find a vacant area that will fit the given bounds nearest the requested
1106 * cell location. Uses Euclidean distance to score multiple vacant areas.
1107 *
1108 * @param pixelX The X location at which you want to search for a vacant area.
1109 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001110 * @param minSpanX The minimum horizontal span required
1111 * @param minSpanY The minimum vertical span required
1112 * @param spanX Horizontal span of the object.
1113 * @param spanY Vertical span of the object.
1114 * @param result Array in which to place the result, or null (in which case a new array will
1115 * be allocated)
1116 * @return The X, Y cell of a vacant area that can contain this object,
1117 * nearest the requested location.
1118 */
1119 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1120 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001121 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001122 result, resultSpan);
1123 }
1124
Adam Cohend41fbf52012-02-16 23:53:59 -08001125 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1126 private void lazyInitTempRectStack() {
1127 if (mTempRectStack.isEmpty()) {
1128 for (int i = 0; i < mCountX * mCountY; i++) {
1129 mTempRectStack.push(new Rect());
1130 }
1131 }
1132 }
Adam Cohen482ed822012-03-02 14:15:13 -08001133
Adam Cohend41fbf52012-02-16 23:53:59 -08001134 private void recycleTempRects(Stack<Rect> used) {
1135 while (!used.isEmpty()) {
1136 mTempRectStack.push(used.pop());
1137 }
1138 }
1139
1140 /**
1141 * Find a vacant area that will fit the given bounds nearest the requested
1142 * cell location. Uses Euclidean distance to score multiple vacant areas.
1143 *
1144 * @param pixelX The X location at which you want to search for a vacant area.
1145 * @param pixelY The Y location at which you want to search for a vacant area.
1146 * @param minSpanX The minimum horizontal span required
1147 * @param minSpanY The minimum vertical span required
1148 * @param spanX Horizontal span of the object.
1149 * @param spanY Vertical span of the object.
1150 * @param ignoreOccupied If true, the result can be an occupied cell
1151 * @param result Array in which to place the result, or null (in which case a new array will
1152 * be allocated)
1153 * @return The X, Y cell of a vacant area that can contain this object,
1154 * nearest the requested location.
1155 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001156 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1157 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001158 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001159
Adam Cohene3e27a82011-04-15 12:07:39 -07001160 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1161 // to the center of the item, but we are searching based on the top-left cell, so
1162 // we translate the point over to correspond to the top-left.
1163 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1164 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1165
Jeff Sharkey70864282009-04-07 21:08:40 -07001166 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001167 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001168 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001169 final Rect bestRect = new Rect(-1, -1, -1, -1);
1170 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001171
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001172 final int countX = mCountX;
1173 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001174
Adam Cohend41fbf52012-02-16 23:53:59 -08001175 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1176 spanX < minSpanX || spanY < minSpanY) {
1177 return bestXY;
1178 }
1179
1180 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001181 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001182 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1183 int ySize = -1;
1184 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001185 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001186 // First, let's see if this thing fits anywhere
1187 for (int i = 0; i < minSpanX; i++) {
1188 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001189 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001190 continue inner;
1191 }
Michael Jurkac28de512010-08-13 11:27:44 -07001192 }
1193 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001194 xSize = minSpanX;
1195 ySize = minSpanY;
1196
1197 // We know that the item will fit at _some_ acceptable size, now let's see
1198 // how big we can make it. We'll alternate between incrementing x and y spans
1199 // until we hit a limit.
1200 boolean incX = true;
1201 boolean hitMaxX = xSize >= spanX;
1202 boolean hitMaxY = ySize >= spanY;
1203 while (!(hitMaxX && hitMaxY)) {
1204 if (incX && !hitMaxX) {
1205 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001206 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001207 // We can't move out horizontally
1208 hitMaxX = true;
1209 }
1210 }
1211 if (!hitMaxX) {
1212 xSize++;
1213 }
1214 } else if (!hitMaxY) {
1215 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001216 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001217 // We can't move out vertically
1218 hitMaxY = true;
1219 }
1220 }
1221 if (!hitMaxY) {
1222 ySize++;
1223 }
1224 }
1225 hitMaxX |= xSize >= spanX;
1226 hitMaxY |= ySize >= spanY;
1227 incX = !incX;
1228 }
1229 incX = true;
1230 hitMaxX = xSize >= spanX;
1231 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001232 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001233 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001234 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001235
Adam Cohend41fbf52012-02-16 23:53:59 -08001236 // We verify that the current rect is not a sub-rect of any of our previous
1237 // candidates. In this case, the current rect is disqualified in favour of the
1238 // containing rect.
1239 Rect currentRect = mTempRectStack.pop();
1240 currentRect.set(x, y, x + xSize, y + ySize);
1241 boolean contained = false;
1242 for (Rect r : validRegions) {
1243 if (r.contains(currentRect)) {
1244 contained = true;
1245 break;
1246 }
1247 }
1248 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001249 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001250
Adam Cohend41fbf52012-02-16 23:53:59 -08001251 if ((distance <= bestDistance && !contained) ||
1252 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001253 bestDistance = distance;
1254 bestXY[0] = x;
1255 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001256 if (resultSpan != null) {
1257 resultSpan[0] = xSize;
1258 resultSpan[1] = ySize;
1259 }
1260 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001261 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001262 }
1263 }
1264
Adam Cohenc0dcf592011-06-01 15:30:43 -07001265 // Return -1, -1 if no suitable location found
1266 if (bestDistance == Double.MAX_VALUE) {
1267 bestXY[0] = -1;
1268 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001269 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001270 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001271 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001272 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001273
Adam Cohen482ed822012-03-02 14:15:13 -08001274 /**
1275 * Find a vacant area that will fit the given bounds nearest the requested
1276 * cell location, and will also weigh in a suggested direction vector of the
1277 * desired location. This method computers distance based on unit grid distances,
1278 * not pixel distances.
1279 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001280 * @param cellX The X cell nearest to which you want to search for a vacant area.
1281 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001282 * @param spanX Horizontal span of the object.
1283 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001284 * @param direction The favored direction in which the views should move from x, y
1285 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1286 * matches exactly. Otherwise we find the best matching direction.
1287 * @param occoupied The array which represents which cells in the CellLayout are occupied
1288 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001289 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001290 * @param result Array in which to place the result, or null (in which case a new array will
1291 * be allocated)
1292 * @return The X, Y cell of a vacant area that can contain this object,
1293 * nearest the requested location.
1294 */
1295 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001296 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001297 // Keep track of best-scoring drop area
1298 final int[] bestXY = result != null ? result : new int[2];
1299 float bestDistance = Float.MAX_VALUE;
1300 int bestDirectionScore = Integer.MIN_VALUE;
1301
1302 final int countX = mCountX;
1303 final int countY = mCountY;
1304
1305 for (int y = 0; y < countY - (spanY - 1); y++) {
1306 inner:
1307 for (int x = 0; x < countX - (spanX - 1); x++) {
1308 // First, let's see if this thing fits anywhere
1309 for (int i = 0; i < spanX; i++) {
1310 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001311 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001312 continue inner;
1313 }
1314 }
1315 }
1316
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001317 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001318 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001319 computeDirectionVector(x - cellX, y - cellY, curDirection);
1320 // The direction score is just the dot product of the two candidate direction
1321 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001322 int curDirectionScore = direction[0] * curDirection[0] +
1323 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001324 boolean exactDirectionOnly = false;
1325 boolean directionMatches = direction[0] == curDirection[0] &&
1326 direction[0] == curDirection[0];
1327 if ((directionMatches || !exactDirectionOnly) &&
1328 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001329 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1330 bestDistance = distance;
1331 bestDirectionScore = curDirectionScore;
1332 bestXY[0] = x;
1333 bestXY[1] = y;
1334 }
1335 }
1336 }
1337
1338 // Return -1, -1 if no suitable location found
1339 if (bestDistance == Float.MAX_VALUE) {
1340 bestXY[0] = -1;
1341 bestXY[1] = -1;
1342 }
1343 return bestXY;
1344 }
1345
1346 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001347 int[] direction, ItemConfiguration currentState) {
1348 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001349 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001350 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001351 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1352
Adam Cohen8baab352012-03-20 17:39:21 -07001353 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001354
1355 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001356 c.x = mTempLocation[0];
1357 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001358 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001359 }
Adam Cohen8baab352012-03-20 17:39:21 -07001360 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001361 return success;
1362 }
1363
Adam Cohenf3900c22012-11-16 18:28:11 -08001364 /**
1365 * This helper class defines a cluster of views. It helps with defining complex edges
1366 * of the cluster and determining how those edges interact with other views. The edges
1367 * essentially define a fine-grained boundary around the cluster of views -- like a more
1368 * precise version of a bounding box.
1369 */
1370 private class ViewCluster {
1371 final static int LEFT = 0;
1372 final static int TOP = 1;
1373 final static int RIGHT = 2;
1374 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001375
Adam Cohenf3900c22012-11-16 18:28:11 -08001376 ArrayList<View> views;
1377 ItemConfiguration config;
1378 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001379
Adam Cohenf3900c22012-11-16 18:28:11 -08001380 int[] leftEdge = new int[mCountY];
1381 int[] rightEdge = new int[mCountY];
1382 int[] topEdge = new int[mCountX];
1383 int[] bottomEdge = new int[mCountX];
1384 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1385
1386 @SuppressWarnings("unchecked")
1387 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1388 this.views = (ArrayList<View>) views.clone();
1389 this.config = config;
1390 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001391 }
1392
Adam Cohenf3900c22012-11-16 18:28:11 -08001393 void resetEdges() {
1394 for (int i = 0; i < mCountX; i++) {
1395 topEdge[i] = -1;
1396 bottomEdge[i] = -1;
1397 }
1398 for (int i = 0; i < mCountY; i++) {
1399 leftEdge[i] = -1;
1400 rightEdge[i] = -1;
1401 }
1402 leftEdgeDirty = true;
1403 rightEdgeDirty = true;
1404 bottomEdgeDirty = true;
1405 topEdgeDirty = true;
1406 boundingRectDirty = true;
1407 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001408
Adam Cohenf3900c22012-11-16 18:28:11 -08001409 void computeEdge(int which, int[] edge) {
1410 int count = views.size();
1411 for (int i = 0; i < count; i++) {
1412 CellAndSpan cs = config.map.get(views.get(i));
1413 switch (which) {
1414 case LEFT:
1415 int left = cs.x;
1416 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1417 if (left < edge[j] || edge[j] < 0) {
1418 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001419 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001420 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001421 break;
1422 case RIGHT:
1423 int right = cs.x + cs.spanX;
1424 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1425 if (right > edge[j]) {
1426 edge[j] = right;
1427 }
1428 }
1429 break;
1430 case TOP:
1431 int top = cs.y;
1432 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1433 if (top < edge[j] || edge[j] < 0) {
1434 edge[j] = top;
1435 }
1436 }
1437 break;
1438 case BOTTOM:
1439 int bottom = cs.y + cs.spanY;
1440 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1441 if (bottom > edge[j]) {
1442 edge[j] = bottom;
1443 }
1444 }
1445 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001446 }
1447 }
1448 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001449
1450 boolean isViewTouchingEdge(View v, int whichEdge) {
1451 CellAndSpan cs = config.map.get(v);
1452
1453 int[] edge = getEdge(whichEdge);
1454
1455 switch (whichEdge) {
1456 case LEFT:
1457 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1458 if (edge[i] == cs.x + cs.spanX) {
1459 return true;
1460 }
1461 }
1462 break;
1463 case RIGHT:
1464 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1465 if (edge[i] == cs.x) {
1466 return true;
1467 }
1468 }
1469 break;
1470 case TOP:
1471 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1472 if (edge[i] == cs.y + cs.spanY) {
1473 return true;
1474 }
1475 }
1476 break;
1477 case BOTTOM:
1478 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1479 if (edge[i] == cs.y) {
1480 return true;
1481 }
1482 }
1483 break;
1484 }
1485 return false;
1486 }
1487
1488 void shift(int whichEdge, int delta) {
1489 for (View v: views) {
1490 CellAndSpan c = config.map.get(v);
1491 switch (whichEdge) {
1492 case LEFT:
1493 c.x -= delta;
1494 break;
1495 case RIGHT:
1496 c.x += delta;
1497 break;
1498 case TOP:
1499 c.y -= delta;
1500 break;
1501 case BOTTOM:
1502 default:
1503 c.y += delta;
1504 break;
1505 }
1506 }
1507 resetEdges();
1508 }
1509
1510 public void addView(View v) {
1511 views.add(v);
1512 resetEdges();
1513 }
1514
1515 public Rect getBoundingRect() {
1516 if (boundingRectDirty) {
1517 boolean first = true;
1518 for (View v: views) {
1519 CellAndSpan c = config.map.get(v);
1520 if (first) {
1521 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1522 first = false;
1523 } else {
1524 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1525 }
1526 }
1527 }
1528 return boundingRect;
1529 }
1530
1531 public int[] getEdge(int which) {
1532 switch (which) {
1533 case LEFT:
1534 return getLeftEdge();
1535 case RIGHT:
1536 return getRightEdge();
1537 case TOP:
1538 return getTopEdge();
1539 case BOTTOM:
1540 default:
1541 return getBottomEdge();
1542 }
1543 }
1544
1545 public int[] getLeftEdge() {
1546 if (leftEdgeDirty) {
1547 computeEdge(LEFT, leftEdge);
1548 }
1549 return leftEdge;
1550 }
1551
1552 public int[] getRightEdge() {
1553 if (rightEdgeDirty) {
1554 computeEdge(RIGHT, rightEdge);
1555 }
1556 return rightEdge;
1557 }
1558
1559 public int[] getTopEdge() {
1560 if (topEdgeDirty) {
1561 computeEdge(TOP, topEdge);
1562 }
1563 return topEdge;
1564 }
1565
1566 public int[] getBottomEdge() {
1567 if (bottomEdgeDirty) {
1568 computeEdge(BOTTOM, bottomEdge);
1569 }
1570 return bottomEdge;
1571 }
1572
1573 PositionComparator comparator = new PositionComparator();
1574 class PositionComparator implements Comparator<View> {
1575 int whichEdge = 0;
1576 public int compare(View left, View right) {
1577 CellAndSpan l = config.map.get(left);
1578 CellAndSpan r = config.map.get(right);
1579 switch (whichEdge) {
1580 case LEFT:
1581 return (r.x + r.spanX) - (l.x + l.spanX);
1582 case RIGHT:
1583 return l.x - r.x;
1584 case TOP:
1585 return (r.y + r.spanY) - (l.y + l.spanY);
1586 case BOTTOM:
1587 default:
1588 return l.y - r.y;
1589 }
1590 }
1591 }
1592
1593 public void sortConfigurationForEdgePush(int edge) {
1594 comparator.whichEdge = edge;
1595 Collections.sort(config.sortedViews, comparator);
1596 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001597 }
1598
Adam Cohenf3900c22012-11-16 18:28:11 -08001599 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1600 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001601
Adam Cohenf3900c22012-11-16 18:28:11 -08001602 ViewCluster cluster = new ViewCluster(views, currentState);
1603 Rect clusterRect = cluster.getBoundingRect();
1604 int whichEdge;
1605 int pushDistance;
1606 boolean fail = false;
1607
1608 // Determine the edge of the cluster that will be leading the push and how far
1609 // the cluster must be shifted.
1610 if (direction[0] < 0) {
1611 whichEdge = ViewCluster.LEFT;
1612 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001613 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001614 whichEdge = ViewCluster.RIGHT;
1615 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1616 } else if (direction[1] < 0) {
1617 whichEdge = ViewCluster.TOP;
1618 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1619 } else {
1620 whichEdge = ViewCluster.BOTTOM;
1621 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001622 }
1623
Adam Cohenf3900c22012-11-16 18:28:11 -08001624 // Break early for invalid push distance.
1625 if (pushDistance <= 0) {
1626 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001627 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001628
1629 // Mark the occupied state as false for the group of views we want to move.
1630 for (View v: views) {
1631 CellAndSpan c = currentState.map.get(v);
1632 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1633 }
1634
1635 // We save the current configuration -- if we fail to find a solution we will revert
1636 // to the initial state. The process of finding a solution modifies the configuration
1637 // in place, hence the need for revert in the failure case.
1638 currentState.save();
1639
1640 // The pushing algorithm is simplified by considering the views in the order in which
1641 // they would be pushed by the cluster. For example, if the cluster is leading with its
1642 // left edge, we consider sort the views by their right edge, from right to left.
1643 cluster.sortConfigurationForEdgePush(whichEdge);
1644
1645 while (pushDistance > 0 && !fail) {
1646 for (View v: currentState.sortedViews) {
1647 // For each view that isn't in the cluster, we see if the leading edge of the
1648 // cluster is contacting the edge of that view. If so, we add that view to the
1649 // cluster.
1650 if (!cluster.views.contains(v) && v != dragView) {
1651 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1652 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1653 if (!lp.canReorder) {
1654 // The push solution includes the all apps button, this is not viable.
1655 fail = true;
1656 break;
1657 }
1658 cluster.addView(v);
1659 CellAndSpan c = currentState.map.get(v);
1660
1661 // Adding view to cluster, mark it as not occupied.
1662 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1663 }
1664 }
1665 }
1666 pushDistance--;
1667
1668 // The cluster has been completed, now we move the whole thing over in the appropriate
1669 // direction.
1670 cluster.shift(whichEdge, 1);
1671 }
1672
1673 boolean foundSolution = false;
1674 clusterRect = cluster.getBoundingRect();
1675
1676 // Due to the nature of the algorithm, the only check required to verify a valid solution
1677 // is to ensure that completed shifted cluster lies completely within the cell layout.
1678 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1679 clusterRect.bottom <= mCountY) {
1680 foundSolution = true;
1681 } else {
1682 currentState.restore();
1683 }
1684
1685 // In either case, we set the occupied array as marked for the location of the views
1686 for (View v: cluster.views) {
1687 CellAndSpan c = currentState.map.get(v);
1688 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1689 }
1690
1691 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001692 }
1693
Adam Cohen482ed822012-03-02 14:15:13 -08001694 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001695 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001696 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001697
Adam Cohen8baab352012-03-20 17:39:21 -07001698 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001699 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001700 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001701 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001702 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001703 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001704 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001705 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001706 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001707 }
1708 }
Adam Cohen8baab352012-03-20 17:39:21 -07001709
Adam Cohen8baab352012-03-20 17:39:21 -07001710 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001711 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001712 CellAndSpan c = currentState.map.get(v);
1713 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1714 }
1715
Adam Cohen47a876d2012-03-19 13:21:41 -07001716 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1717 int top = boundingRect.top;
1718 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001719 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001720 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001721 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001722 CellAndSpan c = currentState.map.get(v);
1723 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001724 }
1725
Adam Cohen482ed822012-03-02 14:15:13 -08001726 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1727
Adam Cohenf3900c22012-11-16 18:28:11 -08001728 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1729 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001730
Adam Cohen8baab352012-03-20 17:39:21 -07001731 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001732 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001733 int deltaX = mTempLocation[0] - boundingRect.left;
1734 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001735 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001736 CellAndSpan c = currentState.map.get(v);
1737 c.x += deltaX;
1738 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001739 }
1740 success = true;
1741 }
Adam Cohen8baab352012-03-20 17:39:21 -07001742
1743 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001744 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001745 CellAndSpan c = currentState.map.get(v);
1746 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001747 }
1748 return success;
1749 }
1750
1751 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1752 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1753 }
1754
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001755 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1756 // to push items in each of the cardinal directions, in an order based on the direction vector
1757 // passed.
1758 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1759 int[] direction, View ignoreView, ItemConfiguration solution) {
1760 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001761 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001762 // separately in each of the components.
1763 int temp = direction[1];
1764 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001765
1766 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001767 ignoreView, solution)) {
1768 return true;
1769 }
1770 direction[1] = temp;
1771 temp = direction[0];
1772 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001773
1774 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001775 ignoreView, solution)) {
1776 return true;
1777 }
1778 // Revert the direction
1779 direction[0] = temp;
1780
1781 // Now we try pushing in each component of the opposite direction
1782 direction[0] *= -1;
1783 direction[1] *= -1;
1784 temp = direction[1];
1785 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001786 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001787 ignoreView, solution)) {
1788 return true;
1789 }
1790
1791 direction[1] = temp;
1792 temp = direction[0];
1793 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001794 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001795 ignoreView, solution)) {
1796 return true;
1797 }
1798 // revert the direction
1799 direction[0] = temp;
1800 direction[0] *= -1;
1801 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001802
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001803 } else {
1804 // If the direction vector has a single non-zero component, we push first in the
1805 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001806 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001807 ignoreView, solution)) {
1808 return true;
1809 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001810 // Then we try the opposite direction
1811 direction[0] *= -1;
1812 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001813 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001814 ignoreView, solution)) {
1815 return true;
1816 }
1817 // Switch the direction back
1818 direction[0] *= -1;
1819 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001820
1821 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001822 // to find a solution by pushing along the perpendicular axis.
1823
1824 // Swap the components
1825 int temp = direction[1];
1826 direction[1] = direction[0];
1827 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001828 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001829 ignoreView, solution)) {
1830 return true;
1831 }
1832
1833 // Then we try the opposite direction
1834 direction[0] *= -1;
1835 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001836 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001837 ignoreView, solution)) {
1838 return true;
1839 }
1840 // Switch the direction back
1841 direction[0] *= -1;
1842 direction[1] *= -1;
1843
1844 // Swap the components back
1845 temp = direction[1];
1846 direction[1] = direction[0];
1847 direction[0] = temp;
1848 }
1849 return false;
1850 }
1851
Adam Cohen482ed822012-03-02 14:15:13 -08001852 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001853 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001854 // Return early if get invalid cell positions
1855 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001856
Adam Cohen8baab352012-03-20 17:39:21 -07001857 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001858 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001859
Adam Cohen8baab352012-03-20 17:39:21 -07001860 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001861 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001862 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001863 if (c != null) {
1864 c.x = cellX;
1865 c.y = cellY;
1866 }
Adam Cohen482ed822012-03-02 14:15:13 -08001867 }
Adam Cohen482ed822012-03-02 14:15:13 -08001868 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1869 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001870 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001871 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001872 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001873 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001874 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001875 if (Rect.intersects(r0, r1)) {
1876 if (!lp.canReorder) {
1877 return false;
1878 }
1879 mIntersectingViews.add(child);
1880 }
1881 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001882
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001883 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1884
Winson Chung5f8afe62013-08-12 16:19:28 -07001885 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001886 // we try to find a solution such that no displaced item travels through another item
1887 // without also displacing that item.
1888 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001889 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001890 return true;
1891 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001892
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001893 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001894 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001895 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001896 return true;
1897 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001898
Adam Cohen482ed822012-03-02 14:15:13 -08001899 // Ok, they couldn't move as a block, let's move them individually
1900 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001901 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001902 return false;
1903 }
1904 }
1905 return true;
1906 }
1907
1908 /*
1909 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1910 * the provided point and the provided cell
1911 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001912 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001913 double angle = Math.atan(((float) deltaY) / deltaX);
1914
1915 result[0] = 0;
1916 result[1] = 0;
1917 if (Math.abs(Math.cos(angle)) > 0.5f) {
1918 result[0] = (int) Math.signum(deltaX);
1919 }
1920 if (Math.abs(Math.sin(angle)) > 0.5f) {
1921 result[1] = (int) Math.signum(deltaY);
1922 }
1923 }
1924
Adam Cohen8baab352012-03-20 17:39:21 -07001925 private void copyOccupiedArray(boolean[][] occupied) {
1926 for (int i = 0; i < mCountX; i++) {
1927 for (int j = 0; j < mCountY; j++) {
1928 occupied[i][j] = mOccupied[i][j];
1929 }
1930 }
1931 }
1932
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001933 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001934 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1935 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001936 // Copy the current state into the solution. This solution will be manipulated as necessary.
1937 copyCurrentStateToSolution(solution, false);
1938 // Copy the current occupied array into the temporary occupied array. This array will be
1939 // manipulated as necessary to find a solution.
1940 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001941
1942 // We find the nearest cell into which we would place the dragged item, assuming there's
1943 // nothing in its way.
1944 int result[] = new int[2];
1945 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1946
1947 boolean success = false;
1948 // First we try the exact nearest position of the item being dragged,
1949 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001950 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1951 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001952
1953 if (!success) {
1954 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1955 // x, then 1 in y etc.
1956 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001957 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1958 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001959 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001960 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1961 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001962 }
1963 solution.isSolution = false;
1964 } else {
1965 solution.isSolution = true;
1966 solution.dragViewX = result[0];
1967 solution.dragViewY = result[1];
1968 solution.dragViewSpanX = spanX;
1969 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001970 }
1971 return solution;
1972 }
1973
1974 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001975 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001976 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001977 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001978 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001979 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001980 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001981 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001982 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001983 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001984 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001985 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001986 }
1987 }
1988
1989 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1990 for (int i = 0; i < mCountX; i++) {
1991 for (int j = 0; j < mCountY; j++) {
1992 mTmpOccupied[i][j] = false;
1993 }
1994 }
1995
Michael Jurkaa52570f2012-03-20 03:18:20 -07001996 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001997 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001998 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001999 if (child == dragView) continue;
2000 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002001 CellAndSpan c = solution.map.get(child);
2002 if (c != null) {
2003 lp.tmpCellX = c.x;
2004 lp.tmpCellY = c.y;
2005 lp.cellHSpan = c.spanX;
2006 lp.cellVSpan = c.spanY;
2007 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002008 }
2009 }
2010 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2011 solution.dragViewSpanY, mTmpOccupied, true);
2012 }
2013
2014 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2015 commitDragView) {
2016
2017 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2018 for (int i = 0; i < mCountX; i++) {
2019 for (int j = 0; j < mCountY; j++) {
2020 occupied[i][j] = false;
2021 }
2022 }
2023
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 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002028 CellAndSpan c = solution.map.get(child);
2029 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002030 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2031 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002032 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002033 }
2034 }
2035 if (commitDragView) {
2036 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2037 solution.dragViewSpanY, occupied, true);
2038 }
2039 }
2040
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002041
2042 // This method starts or changes the reorder preview animations
2043 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2044 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002045 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002046 for (int i = 0; i < childCount; i++) {
2047 View child = mShortcutsAndWidgets.getChildAt(i);
2048 if (child == dragView) continue;
2049 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002050 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2051 != null && !solution.intersectingViews.contains(child);
2052
Adam Cohen19f37922012-03-21 11:59:11 -07002053 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002054 if (c != null && !skip) {
2055 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2056 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002057 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002058 }
2059 }
2060 }
2061
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002062 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002063 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002064 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002065 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002066 float finalDeltaX;
2067 float finalDeltaY;
2068 float initDeltaX;
2069 float initDeltaY;
2070 float finalScale;
2071 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002072 int mode;
2073 boolean repeating = false;
2074 private static final int PREVIEW_DURATION = 300;
2075 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2076
2077 public static final int MODE_HINT = 0;
2078 public static final int MODE_PREVIEW = 1;
2079
Adam Cohene7587d22012-05-24 18:50:02 -07002080 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002081
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002082 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2083 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002084 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2085 final int x0 = mTmpPoint[0];
2086 final int y0 = mTmpPoint[1];
2087 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2088 final int x1 = mTmpPoint[0];
2089 final int y1 = mTmpPoint[1];
2090 final int dX = x1 - x0;
2091 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002092 finalDeltaX = 0;
2093 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002094 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002095 if (dX == dY && dX == 0) {
2096 } else {
2097 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002098 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002099 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002100 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002101 } else {
2102 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002103 finalDeltaX = (int) (- dir * Math.signum(dX) *
2104 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2105 finalDeltaY = (int) (- dir * Math.signum(dY) *
2106 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002107 }
2108 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002109 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002110 initDeltaX = child.getTranslationX();
2111 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002112 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002113 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002114 this.child = child;
2115 }
2116
Adam Cohend024f982012-05-23 18:26:45 -07002117 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002118 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002119 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002120 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002121 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002122 if (finalDeltaX == 0 && finalDeltaY == 0) {
2123 completeAnimationImmediately();
2124 return;
2125 }
Adam Cohen19f37922012-03-21 11:59:11 -07002126 }
Adam Cohend024f982012-05-23 18:26:45 -07002127 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002128 return;
2129 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002130 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002131 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002132 va.setRepeatMode(ValueAnimator.REVERSE);
2133 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002134 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002135 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002136 va.addUpdateListener(new AnimatorUpdateListener() {
2137 @Override
2138 public void onAnimationUpdate(ValueAnimator animation) {
2139 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002140 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2141 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2142 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002143 child.setTranslationX(x);
2144 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002145 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002146 child.setScaleX(s);
2147 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002148 }
2149 });
2150 va.addListener(new AnimatorListenerAdapter() {
2151 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002152 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002153 initDeltaX = 0;
2154 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002155 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002156 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002157 }
2158 });
Adam Cohen19f37922012-03-21 11:59:11 -07002159 mShakeAnimators.put(child, this);
2160 va.start();
2161 }
2162
Adam Cohend024f982012-05-23 18:26:45 -07002163 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002164 if (a != null) {
2165 a.cancel();
2166 }
Adam Cohen19f37922012-03-21 11:59:11 -07002167 }
Adam Cohene7587d22012-05-24 18:50:02 -07002168
Adam Cohen091440a2015-03-18 14:16:05 -07002169 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002170 if (a != null) {
2171 a.cancel();
2172 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002173
Michael Jurka2ecf9952012-06-18 12:52:28 -07002174 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002175 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002176 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002177 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2178 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002179 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2180 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002181 );
2182 s.setDuration(REORDER_ANIMATION_DURATION);
2183 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2184 s.start();
2185 }
Adam Cohen19f37922012-03-21 11:59:11 -07002186 }
2187
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002188 private void completeAndClearReorderPreviewAnimations() {
2189 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002190 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002191 }
2192 mShakeAnimators.clear();
2193 }
2194
Adam Cohen482ed822012-03-02 14:15:13 -08002195 private void commitTempPlacement() {
2196 for (int i = 0; i < mCountX; i++) {
2197 for (int j = 0; j < mCountY; j++) {
2198 mOccupied[i][j] = mTmpOccupied[i][j];
2199 }
2200 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002201 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002202 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002203 View child = mShortcutsAndWidgets.getChildAt(i);
2204 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2205 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002206 // We do a null check here because the item info can be null in the case of the
2207 // AllApps button in the hotseat.
2208 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002209 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2210 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2211 info.requiresDbUpdate = true;
2212 }
Adam Cohen2acce882012-03-28 19:03:19 -07002213 info.cellX = lp.cellX = lp.tmpCellX;
2214 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002215 info.spanX = lp.cellHSpan;
2216 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002217 }
Adam Cohen482ed822012-03-02 14:15:13 -08002218 }
Adam Cohen2acce882012-03-28 19:03:19 -07002219 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002220 }
2221
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002222 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002223 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002224 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002225 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002226 lp.useTmpCoords = useTempCoords;
2227 }
2228 }
2229
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002230 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002231 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2232 int[] result = new int[2];
2233 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002234 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002235 resultSpan);
2236 if (result[0] >= 0 && result[1] >= 0) {
2237 copyCurrentStateToSolution(solution, false);
2238 solution.dragViewX = result[0];
2239 solution.dragViewY = result[1];
2240 solution.dragViewSpanX = resultSpan[0];
2241 solution.dragViewSpanY = resultSpan[1];
2242 solution.isSolution = true;
2243 } else {
2244 solution.isSolution = false;
2245 }
2246 return solution;
2247 }
2248
2249 public void prepareChildForDrag(View child) {
2250 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002251 }
2252
Adam Cohen19f37922012-03-21 11:59:11 -07002253 /* This seems like it should be obvious and straight-forward, but when the direction vector
2254 needs to match with the notion of the dragView pushing other views, we have to employ
2255 a slightly more subtle notion of the direction vector. The question is what two points is
2256 the vector between? The center of the dragView and its desired destination? Not quite, as
2257 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2258 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2259 or right, which helps make pushing feel right.
2260 */
2261 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2262 int spanY, View dragView, int[] resultDirection) {
2263 int[] targetDestination = new int[2];
2264
2265 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2266 Rect dragRect = new Rect();
2267 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2268 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2269
2270 Rect dropRegionRect = new Rect();
2271 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2272 dragView, dropRegionRect, mIntersectingViews);
2273
2274 int dropRegionSpanX = dropRegionRect.width();
2275 int dropRegionSpanY = dropRegionRect.height();
2276
2277 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2278 dropRegionRect.height(), dropRegionRect);
2279
2280 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2281 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2282
2283 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2284 deltaX = 0;
2285 }
2286 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2287 deltaY = 0;
2288 }
2289
2290 if (deltaX == 0 && deltaY == 0) {
2291 // No idea what to do, give a random direction.
2292 resultDirection[0] = 1;
2293 resultDirection[1] = 0;
2294 } else {
2295 computeDirectionVector(deltaX, deltaY, resultDirection);
2296 }
2297 }
2298
2299 // For a given cell and span, fetch the set of views intersecting the region.
2300 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2301 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2302 if (boundingRect != null) {
2303 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2304 }
2305 intersectingViews.clear();
2306 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2307 Rect r1 = new Rect();
2308 final int count = mShortcutsAndWidgets.getChildCount();
2309 for (int i = 0; i < count; i++) {
2310 View child = mShortcutsAndWidgets.getChildAt(i);
2311 if (child == dragView) continue;
2312 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2313 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2314 if (Rect.intersects(r0, r1)) {
2315 mIntersectingViews.add(child);
2316 if (boundingRect != null) {
2317 boundingRect.union(r1);
2318 }
2319 }
2320 }
2321 }
2322
2323 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2324 View dragView, int[] result) {
2325 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2326 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2327 mIntersectingViews);
2328 return !mIntersectingViews.isEmpty();
2329 }
2330
2331 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002332 completeAndClearReorderPreviewAnimations();
2333 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2334 final int count = mShortcutsAndWidgets.getChildCount();
2335 for (int i = 0; i < count; i++) {
2336 View child = mShortcutsAndWidgets.getChildAt(i);
2337 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2338 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2339 lp.tmpCellX = lp.cellX;
2340 lp.tmpCellY = lp.cellY;
2341 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2342 0, false, false);
2343 }
Adam Cohen19f37922012-03-21 11:59:11 -07002344 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002345 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002346 }
Adam Cohen19f37922012-03-21 11:59:11 -07002347 }
2348
Adam Cohenbebf0422012-04-11 18:06:28 -07002349 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2350 View dragView, int[] direction, boolean commit) {
2351 int[] pixelXY = new int[2];
2352 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2353
2354 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002355 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002356 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2357
2358 setUseTempCoords(true);
2359 if (swapSolution != null && swapSolution.isSolution) {
2360 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2361 // committing anything or animating anything as we just want to determine if a solution
2362 // exists
2363 copySolutionToTempState(swapSolution, dragView);
2364 setItemPlacementDirty(true);
2365 animateItemsToSolution(swapSolution, dragView, commit);
2366
2367 if (commit) {
2368 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002369 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002370 setItemPlacementDirty(false);
2371 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002372 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2373 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002374 }
2375 mShortcutsAndWidgets.requestLayout();
2376 }
2377 return swapSolution.isSolution;
2378 }
2379
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002380 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002381 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002382 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002383 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002384
2385 if (resultSpan == null) {
2386 resultSpan = new int[2];
2387 }
2388
Adam Cohen19f37922012-03-21 11:59:11 -07002389 // When we are checking drop validity or actually dropping, we don't recompute the
2390 // direction vector, since we want the solution to match the preview, and it's possible
2391 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002392 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2393 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002394 mDirectionVector[0] = mPreviousReorderDirection[0];
2395 mDirectionVector[1] = mPreviousReorderDirection[1];
2396 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002397 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2398 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2399 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002400 }
2401 } else {
2402 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2403 mPreviousReorderDirection[0] = mDirectionVector[0];
2404 mPreviousReorderDirection[1] = mDirectionVector[1];
2405 }
2406
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002407 // Find a solution involving pushing / displacing any items in the way
2408 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002409 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2410
2411 // We attempt the approach which doesn't shuffle views at all
2412 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2413 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2414
2415 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002416
2417 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2418 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002419 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2420 finalSolution = swapSolution;
2421 } else if (noShuffleSolution.isSolution) {
2422 finalSolution = noShuffleSolution;
2423 }
2424
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002425 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002426 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002427 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2428 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002429 result[0] = finalSolution.dragViewX;
2430 result[1] = finalSolution.dragViewY;
2431 resultSpan[0] = finalSolution.dragViewSpanX;
2432 resultSpan[1] = finalSolution.dragViewSpanY;
2433 } else {
2434 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2435 }
2436 return result;
2437 }
2438
Adam Cohen482ed822012-03-02 14:15:13 -08002439 boolean foundSolution = true;
2440 if (!DESTRUCTIVE_REORDER) {
2441 setUseTempCoords(true);
2442 }
2443
2444 if (finalSolution != null) {
2445 result[0] = finalSolution.dragViewX;
2446 result[1] = finalSolution.dragViewY;
2447 resultSpan[0] = finalSolution.dragViewSpanX;
2448 resultSpan[1] = finalSolution.dragViewSpanY;
2449
2450 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2451 // committing anything or animating anything as we just want to determine if a solution
2452 // exists
2453 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2454 if (!DESTRUCTIVE_REORDER) {
2455 copySolutionToTempState(finalSolution, dragView);
2456 }
2457 setItemPlacementDirty(true);
2458 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2459
Adam Cohen19f37922012-03-21 11:59:11 -07002460 if (!DESTRUCTIVE_REORDER &&
2461 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002462 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002463 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002464 setItemPlacementDirty(false);
2465 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002466 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2467 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002468 }
2469 }
2470 } else {
2471 foundSolution = false;
2472 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2473 }
2474
2475 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2476 setUseTempCoords(false);
2477 }
Adam Cohen482ed822012-03-02 14:15:13 -08002478
Michael Jurkaa52570f2012-03-20 03:18:20 -07002479 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002480 return result;
2481 }
2482
Adam Cohen19f37922012-03-21 11:59:11 -07002483 void setItemPlacementDirty(boolean dirty) {
2484 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002485 }
Adam Cohen19f37922012-03-21 11:59:11 -07002486 boolean isItemPlacementDirty() {
2487 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002488 }
2489
Adam Cohen091440a2015-03-18 14:16:05 -07002490 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002491 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002492 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2493 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002494 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002495 boolean isSolution = false;
2496 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2497
Adam Cohenf3900c22012-11-16 18:28:11 -08002498 void save() {
2499 // Copy current state into savedMap
2500 for (View v: map.keySet()) {
2501 map.get(v).copy(savedMap.get(v));
2502 }
2503 }
2504
2505 void restore() {
2506 // Restore current state from savedMap
2507 for (View v: savedMap.keySet()) {
2508 savedMap.get(v).copy(map.get(v));
2509 }
2510 }
2511
2512 void add(View v, CellAndSpan cs) {
2513 map.put(v, cs);
2514 savedMap.put(v, new CellAndSpan());
2515 sortedViews.add(v);
2516 }
2517
Adam Cohen482ed822012-03-02 14:15:13 -08002518 int area() {
2519 return dragViewSpanX * dragViewSpanY;
2520 }
Adam Cohen8baab352012-03-20 17:39:21 -07002521 }
2522
2523 private class CellAndSpan {
2524 int x, y;
2525 int spanX, spanY;
2526
Adam Cohenf3900c22012-11-16 18:28:11 -08002527 public CellAndSpan() {
2528 }
2529
2530 public void copy(CellAndSpan copy) {
2531 copy.x = x;
2532 copy.y = y;
2533 copy.spanX = spanX;
2534 copy.spanY = spanY;
2535 }
2536
Adam Cohen8baab352012-03-20 17:39:21 -07002537 public CellAndSpan(int x, int y, int spanX, int spanY) {
2538 this.x = x;
2539 this.y = y;
2540 this.spanX = spanX;
2541 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002542 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002543
2544 public String toString() {
2545 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2546 }
2547
Adam Cohen482ed822012-03-02 14:15:13 -08002548 }
2549
Adam Cohendf035382011-04-11 17:22:04 -07002550 /**
Adam Cohendf035382011-04-11 17:22:04 -07002551 * Find a starting cell position that will fit the given bounds nearest the requested
2552 * cell location. Uses Euclidean distance to score multiple vacant areas.
2553 *
2554 * @param pixelX The X location at which you want to search for a vacant area.
2555 * @param pixelY The Y location at which you want to search for a vacant area.
2556 * @param spanX Horizontal span of the object.
2557 * @param spanY Vertical span of the object.
2558 * @param ignoreView Considers space occupied by this view as unoccupied
2559 * @param result Previously returned value to possibly recycle.
2560 * @return The X, Y cell of a vacant area that can contain this object,
2561 * nearest the requested location.
2562 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002563 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2564 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002565 }
2566
Michael Jurka0280c3b2010-09-17 15:00:07 -07002567 boolean existsEmptyCell() {
2568 return findCellForSpan(null, 1, 1);
2569 }
2570
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002571 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002572 * Finds the upper-left coordinate of the first rectangle in the grid that can
2573 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2574 * then this method will only return coordinates for rectangles that contain the cell
2575 * (intersectX, intersectY)
2576 *
2577 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2578 * can be found.
2579 * @param spanX The horizontal span of the cell we want to find.
2580 * @param spanY The vertical span of the cell we want to find.
2581 *
2582 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002583 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002584 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002585 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002586 final int endX = mCountX - (spanX - 1);
2587 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002588
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002589 for (int y = 0; y < endY && !foundCell; y++) {
2590 inner:
2591 for (int x = 0; x < endX; x++) {
2592 for (int i = 0; i < spanX; i++) {
2593 for (int j = 0; j < spanY; j++) {
2594 if (mOccupied[x + i][y + j]) {
2595 // small optimization: we can skip to after the column we just found
2596 // an occupied cell
2597 x += i;
2598 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002599 }
2600 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002601 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002602 if (cellXY != null) {
2603 cellXY[0] = x;
2604 cellXY[1] = y;
2605 }
2606 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002607 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002608 }
2609 }
2610
Michael Jurka28750fb2010-09-24 17:43:49 -07002611 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002612 }
2613
2614 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002615 * A drag event has begun over this layout.
2616 * It may have begun over this layout (in which case onDragChild is called first),
2617 * or it may have begun on another layout.
2618 */
2619 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002620 mDragging = true;
2621 }
2622
2623 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002624 * Called when drag has left this CellLayout or has been completed (successfully or not)
2625 */
2626 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002627 // This can actually be called when we aren't in a drag, e.g. when adding a new
2628 // item to this layout via the customize drawer.
2629 // Guard against that case.
2630 if (mDragging) {
2631 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002632 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002633
2634 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002635 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002636 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2637 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002638 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002639 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002640 }
2641
2642 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002643 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002644 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002645 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002646 *
2647 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002648 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002649 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002650 if (child != null) {
2651 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002652 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002653 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002654 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002655 }
2656
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002657 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002658 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002659 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002660 * @param cellX X coordinate of upper left corner expressed as a cell position
2661 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002662 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002663 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002664 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002665 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002666 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002667 final int cellWidth = mCellWidth;
2668 final int cellHeight = mCellHeight;
2669 final int widthGap = mWidthGap;
2670 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002671
Winson Chung4b825dcd2011-06-19 12:41:22 -07002672 final int hStartPadding = getPaddingLeft();
2673 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002674
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002675 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2676 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2677
2678 int x = hStartPadding + cellX * (cellWidth + widthGap);
2679 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002680
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002681 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002682 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002683
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002684 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002685 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002686 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002687 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002688 * @param width Width in pixels
2689 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002690 * @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 -08002691 */
Adam Cohen2e6da152015-05-06 11:42:25 -07002692 public static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
Adam Cohen2e6da152015-05-06 11:42:25 -07002693 DeviceProfile grid = launcher.getDeviceProfile();
Sunny Goyalc6205602015-05-21 20:46:33 -07002694 Rect padding = grid.getWorkspacePadding(Utilities.isRtl(launcher.getResources()));
Winson Chung5f8afe62013-08-12 16:19:28 -07002695
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002696 // Always assume we're working with the smallest span to make sure we
2697 // reserve enough space in both orientations.
Sunny Goyalc6205602015-05-21 20:46:33 -07002698 int parentWidth = DeviceProfile.calculateCellWidth(grid.widthPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002699 - padding.left - padding.right, (int) grid.inv.numColumns);
Sunny Goyalc6205602015-05-21 20:46:33 -07002700 int parentHeight = DeviceProfile.calculateCellHeight(grid.heightPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002701 - padding.top - padding.bottom, (int) grid.inv.numRows);
Winson Chung66700732013-08-20 16:56:15 -07002702 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002703
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002704 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002705 int spanX = (int) Math.ceil(width / (float) smallerSize);
2706 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002707
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002708 if (result == null) {
2709 return new int[] { spanX, spanY };
2710 }
2711 result[0] = spanX;
2712 result[1] = spanY;
2713 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002714 }
2715
2716 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002717 * Calculate the grid spans needed to fit given item
2718 */
2719 public void calculateSpans(ItemInfo info) {
2720 final int minWidth;
2721 final int minHeight;
2722
2723 if (info instanceof LauncherAppWidgetInfo) {
2724 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2725 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2726 } else if (info instanceof PendingAddWidgetInfo) {
2727 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2728 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2729 } else {
2730 // It's not a widget, so it must be 1x1
2731 info.spanX = info.spanY = 1;
2732 return;
2733 }
Adam Cohen2e6da152015-05-06 11:42:25 -07002734 int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
Patrick Dubroy047379a2010-12-19 22:02:04 -08002735 info.spanX = spans[0];
2736 info.spanY = spans[1];
2737 }
2738
Michael Jurka0280c3b2010-09-17 15:00:07 -07002739 private void clearOccupiedCells() {
2740 for (int x = 0; x < mCountX; x++) {
2741 for (int y = 0; y < mCountY; y++) {
2742 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002743 }
2744 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002745 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002746
Adam Cohend4844c32011-02-18 19:25:06 -08002747 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002748 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002749 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002750 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002751 }
2752
Adam Cohend4844c32011-02-18 19:25:06 -08002753 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002754 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002755 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002756 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002757 }
2758
Adam Cohen482ed822012-03-02 14:15:13 -08002759 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2760 boolean value) {
2761 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002762 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2763 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002764 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002765 }
2766 }
2767 }
2768
Adam Cohen2801caf2011-05-13 20:57:39 -07002769 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002770 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002771 (Math.max((mCountX - 1), 0) * mWidthGap);
2772 }
2773
2774 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002775 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002776 (Math.max((mCountY - 1), 0) * mHeightGap);
2777 }
2778
Michael Jurka66d72172011-04-12 16:29:25 -07002779 public boolean isOccupied(int x, int y) {
2780 if (x < mCountX && y < mCountY) {
2781 return mOccupied[x][y];
2782 } else {
2783 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2784 }
2785 }
2786
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002787 @Override
2788 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2789 return new CellLayout.LayoutParams(getContext(), attrs);
2790 }
2791
2792 @Override
2793 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2794 return p instanceof CellLayout.LayoutParams;
2795 }
2796
2797 @Override
2798 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2799 return new CellLayout.LayoutParams(p);
2800 }
2801
2802 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2803 /**
2804 * Horizontal location of the item in the grid.
2805 */
2806 @ViewDebug.ExportedProperty
2807 public int cellX;
2808
2809 /**
2810 * Vertical location of the item in the grid.
2811 */
2812 @ViewDebug.ExportedProperty
2813 public int cellY;
2814
2815 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002816 * Temporary horizontal location of the item in the grid during reorder
2817 */
2818 public int tmpCellX;
2819
2820 /**
2821 * Temporary vertical location of the item in the grid during reorder
2822 */
2823 public int tmpCellY;
2824
2825 /**
2826 * Indicates that the temporary coordinates should be used to layout the items
2827 */
2828 public boolean useTmpCoords;
2829
2830 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002831 * Number of cells spanned horizontally by the item.
2832 */
2833 @ViewDebug.ExportedProperty
2834 public int cellHSpan;
2835
2836 /**
2837 * Number of cells spanned vertically by the item.
2838 */
2839 @ViewDebug.ExportedProperty
2840 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002841
Adam Cohen1b607ed2011-03-03 17:26:50 -08002842 /**
2843 * Indicates whether the item will set its x, y, width and height parameters freely,
2844 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2845 */
Adam Cohend4844c32011-02-18 19:25:06 -08002846 public boolean isLockedToGrid = true;
2847
Adam Cohen482ed822012-03-02 14:15:13 -08002848 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002849 * Indicates that this item should use the full extents of its parent.
2850 */
2851 public boolean isFullscreen = false;
2852
2853 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002854 * Indicates whether this item can be reordered. Always true except in the case of the
2855 * the AllApps button.
2856 */
2857 public boolean canReorder = true;
2858
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002859 // X coordinate of the view in the layout.
2860 @ViewDebug.ExportedProperty
2861 int x;
2862 // Y coordinate of the view in the layout.
2863 @ViewDebug.ExportedProperty
2864 int y;
2865
Romain Guy84f296c2009-11-04 15:00:44 -08002866 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002867
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002868 public LayoutParams(Context c, AttributeSet attrs) {
2869 super(c, attrs);
2870 cellHSpan = 1;
2871 cellVSpan = 1;
2872 }
2873
2874 public LayoutParams(ViewGroup.LayoutParams source) {
2875 super(source);
2876 cellHSpan = 1;
2877 cellVSpan = 1;
2878 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002879
2880 public LayoutParams(LayoutParams source) {
2881 super(source);
2882 this.cellX = source.cellX;
2883 this.cellY = source.cellY;
2884 this.cellHSpan = source.cellHSpan;
2885 this.cellVSpan = source.cellVSpan;
2886 }
2887
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002888 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002889 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002890 this.cellX = cellX;
2891 this.cellY = cellY;
2892 this.cellHSpan = cellHSpan;
2893 this.cellVSpan = cellVSpan;
2894 }
2895
Adam Cohen2374abf2013-04-16 14:56:57 -07002896 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2897 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002898 if (isLockedToGrid) {
2899 final int myCellHSpan = cellHSpan;
2900 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002901 int myCellX = useTmpCoords ? tmpCellX : cellX;
2902 int myCellY = useTmpCoords ? tmpCellY : cellY;
2903
2904 if (invertHorizontally) {
2905 myCellX = colCount - myCellX - cellHSpan;
2906 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002907
Adam Cohend4844c32011-02-18 19:25:06 -08002908 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2909 leftMargin - rightMargin;
2910 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2911 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002912 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2913 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002914 }
2915 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002916
Winson Chungaafa03c2010-06-11 17:34:16 -07002917 public String toString() {
2918 return "(" + this.cellX + ", " + this.cellY + ")";
2919 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002920
2921 public void setWidth(int width) {
2922 this.width = width;
2923 }
2924
2925 public int getWidth() {
2926 return width;
2927 }
2928
2929 public void setHeight(int height) {
2930 this.height = height;
2931 }
2932
2933 public int getHeight() {
2934 return height;
2935 }
2936
2937 public void setX(int x) {
2938 this.x = x;
2939 }
2940
2941 public int getX() {
2942 return x;
2943 }
2944
2945 public void setY(int y) {
2946 this.y = y;
2947 }
2948
2949 public int getY() {
2950 return y;
2951 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002952 }
2953
Michael Jurka0280c3b2010-09-17 15:00:07 -07002954 // This class stores info for two purposes:
2955 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2956 // its spanX, spanY, and the screen it is on
2957 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2958 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2959 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002960 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002961 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002962 int cellX = -1;
2963 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002964 int spanX;
2965 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002966 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002967 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002968
Sunny Goyal83a8f042015-05-19 12:52:12 -07002969 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002970 cell = v;
2971 cellX = info.cellX;
2972 cellY = info.cellY;
2973 spanX = info.spanX;
2974 spanY = info.spanY;
2975 screenId = info.screenId;
2976 container = info.container;
2977 }
2978
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002979 @Override
2980 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002981 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2982 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002983 }
2984 }
Michael Jurkad771c962011-08-09 15:00:48 -07002985
Sunny Goyala9116722015-04-29 13:55:58 -07002986 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2987 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2988 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002989
2990 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
2991 int x2 = x + spanX - 1;
2992 int y2 = y + spanY - 1;
2993 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
2994 return false;
2995 }
2996 for (int i = x; i <= x2; i++) {
2997 for (int j = y; j <= y2; j++) {
2998 if (mOccupied[i][j]) {
2999 return false;
3000 }
3001 }
3002 }
3003
3004 return true;
3005 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003006}