blob: 2b1cfe0e4e9045092fa60a49b56f6c9879e37ff8 [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;
98
Adam Cohen69ce2e52011-07-03 19:25:21 -070099 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700100 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700101
Sunny Goyal2805e632015-05-20 15:35:32 -0700102 private static final float FOREGROUND_ALPHA_DAMPER = 0.65f;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700103 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700104 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700105
Sunny Goyal2805e632015-05-20 15:35:32 -0700106 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
107 private final TransitionDrawable mBackground;
108
109 private final Drawable mOverScrollLeft;
110 private final Drawable mOverScrollRight;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700111 private Drawable mOverScrollForegroundDrawable;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700112
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700113 // These values allow a fixed measurement to be set on the CellLayout.
114 private int mFixedWidth = -1;
115 private int mFixedHeight = -1;
116
Michael Jurka33945b22010-12-21 18:19:38 -0800117 // If we're actively dragging something over this screen, mIsDragOverlapping is true
118 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700119
Winson Chung150fbab2010-09-29 17:14:26 -0700120 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700121 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700122 @Thunk Rect[] mDragOutlines = new Rect[4];
123 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700124 private InterruptibleInOutAnimator[] mDragOutlineAnims =
125 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700126
127 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700128 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700129 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700130
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700131 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800132
Adam Cohen091440a2015-03-18 14:16:05 -0700133 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
Adam Cohen482ed822012-03-02 14:15:13 -0800134 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800135 private HashMap<View, ReorderPreviewAnimation>
136 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700137
138 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700139
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700140 // When a drag operation is in progress, holds the nearest cell to the touch point
141 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800142
Joe Onorato4be866d2010-10-10 11:26:02 -0700143 private boolean mDragging = false;
144
Patrick Dubroyce34a972010-10-19 10:34:32 -0700145 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700146 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700147
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800148 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700149 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800150
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800151 public static final int MODE_SHOW_REORDER_HINT = 0;
152 public static final int MODE_DRAG_OVER = 1;
153 public static final int MODE_ON_DROP = 2;
154 public static final int MODE_ON_DROP_EXTERNAL = 3;
155 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700156 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800157 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
158
Adam Cohena897f392012-04-27 18:12:05 -0700159 static final int LANDSCAPE = 0;
160 static final int PORTRAIT = 1;
161
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800162 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700163 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700164 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700165
Adam Cohen482ed822012-03-02 14:15:13 -0800166 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
167 private Rect mOccupiedRect = new Rect();
168 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700169 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700170 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700171 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800172
Sunny Goyal2805e632015-05-20 15:35:32 -0700173 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700174
Michael Jurkaca993832012-06-29 15:17:04 -0700175 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700176
Adam Cohenc9735cf2015-01-23 16:11:55 -0800177 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700178 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800179 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800180
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800181 public CellLayout(Context context) {
182 this(context, null);
183 }
184
185 public CellLayout(Context context, AttributeSet attrs) {
186 this(context, attrs, 0);
187 }
188
189 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
190 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700191 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700192
193 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
194 // the user where a dragged item will land when dropped.
195 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800196 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700197 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700198
Adam Cohen2e6da152015-05-06 11:42:25 -0700199 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800200 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
201
Winson Chung11a1a532013-09-13 11:14:45 -0700202 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800203 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700204 mWidthGap = mOriginalWidthGap = 0;
205 mHeightGap = mOriginalHeightGap = 0;
206 mMaxGap = Integer.MAX_VALUE;
Adam Cohen2e6da152015-05-06 11:42:25 -0700207 mCountX = (int) grid.inv.numColumns;
208 mCountY = (int) grid.inv.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700209 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800210 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700211 mPreviousReorderDirection[0] = INVALID_DIRECTION;
212 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800213
214 a.recycle();
215
216 setAlwaysDrawnWithCacheEnabled(false);
217
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700218 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700219 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700220
Sunny Goyal2805e632015-05-20 15:35:32 -0700221 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
222 mBackground.setCallback(this);
Michael Jurka33945b22010-12-21 18:19:38 -0800223
Adam Cohenb5ba0972011-09-07 18:02:31 -0700224 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
225 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
Michael Jurka33945b22010-12-21 18:19:38 -0800226
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800227 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700228 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700229
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700230 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700231 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700232 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700233 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800234 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700235 }
236
237 // When dragging things around the home screens, we show a green outline of
238 // where the item will land. The outlines gradually fade out, leaving a trail
239 // behind the drag path.
240 // Set up all the animations that are used to implement this fading.
241 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700242 final float fromAlphaValue = 0;
243 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700244
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700245 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700246
247 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700248 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100249 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700250 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700251 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700252 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700253 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700254 final Bitmap outline = (Bitmap)anim.getTag();
255
256 // If an animation is started and then stopped very quickly, we can still
257 // get spurious updates we've cleared the tag. Guard against this.
258 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700259 @SuppressWarnings("all") // suppress dead code warning
260 final boolean debug = false;
261 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700262 Object val = animation.getAnimatedValue();
263 Log.d(TAG, "anim " + thisIndex + " update: " + val +
264 ", isStopped " + anim.isStopped());
265 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700266 // Try to prevent it from continuing to run
267 animation.cancel();
268 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700269 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800270 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700271 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700272 }
273 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700274 // The animation holds a reference to the drag outline bitmap as long is it's
275 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700276 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700277 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700278 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700279 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700280 anim.setTag(null);
281 }
282 }
283 });
284 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700285 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700286
Michael Jurkaa52570f2012-03-20 03:18:20 -0700287 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700288 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700289 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700290
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700291 mTouchFeedbackView = new ClickShadowView(context);
292 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700293 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700294 }
295
Adam Cohenc9735cf2015-01-23 16:11:55 -0800296 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700297 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800298 mUseTouchHelper = enable;
299 if (!enable) {
300 ViewCompat.setAccessibilityDelegate(this, null);
301 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
302 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
303 setOnClickListener(mLauncher);
304 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700305 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
306 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
307 mTouchHelper = new WorkspaceAccessibilityHelper(this);
308 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
309 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
310 mTouchHelper = new FolderAccessibilityHelper(this);
311 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800312 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
313 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
314 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
315 setOnClickListener(mTouchHelper);
316 }
317
318 // Invalidate the accessibility hierarchy
319 if (getParent() != null) {
320 getParent().notifySubtreeAccessibilityStateChanged(
321 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
322 }
323 }
324
325 @Override
326 public boolean dispatchHoverEvent(MotionEvent event) {
327 // Always attempt to dispatch hover events to accessibility first.
328 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
329 return true;
330 }
331 return super.dispatchHoverEvent(event);
332 }
333
334 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800335 public boolean onInterceptTouchEvent(MotionEvent ev) {
336 if (mUseTouchHelper ||
337 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
338 return true;
339 }
340 return false;
341 }
342
Chris Craik01f2d7f2013-10-01 14:41:56 -0700343 public void enableHardwareLayer(boolean hasLayer) {
344 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700345 }
346
347 public void buildHardwareLayer() {
348 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700349 }
350
Adam Cohen307fe232012-08-16 17:55:58 -0700351 public float getChildrenScale() {
352 return mIsHotseat ? mHotseatScale : 1.0f;
353 }
354
Winson Chung5f8afe62013-08-12 16:19:28 -0700355 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700356 mFixedCellWidth = mCellWidth = width;
357 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700358 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
359 mCountX, mCountY);
360 }
361
Adam Cohen2801caf2011-05-13 20:57:39 -0700362 public void setGridSize(int x, int y) {
363 mCountX = x;
364 mCountY = y;
365 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800366 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700367 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700368 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700369 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700370 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700371 }
372
Adam Cohen2374abf2013-04-16 14:56:57 -0700373 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
374 public void setInvertIfRtl(boolean invert) {
375 mShortcutsAndWidgets.setInvertIfRtl(invert);
376 }
377
Adam Cohen917e3882013-10-31 15:03:35 -0700378 public void setDropPending(boolean pending) {
379 mDropPending = pending;
380 }
381
382 public boolean isDropPending() {
383 return mDropPending;
384 }
385
Adam Cohenb5ba0972011-09-07 18:02:31 -0700386 void setOverScrollAmount(float r, boolean left) {
387 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
388 mOverScrollForegroundDrawable = mOverScrollLeft;
389 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
390 mOverScrollForegroundDrawable = mOverScrollRight;
391 }
392
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700393 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700394 mForegroundAlpha = (int) Math.round((r * 255));
395 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
396 invalidate();
397 }
398
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700399 @Override
400 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700401 if (icon == null || background == null) {
402 mTouchFeedbackView.setBitmap(null);
403 mTouchFeedbackView.animate().cancel();
404 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700405 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700406 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
407 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700408 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800409 }
410 }
411
Adam Cohenc50438c2014-08-19 17:43:05 -0700412 void disableDragTarget() {
413 mIsDragTarget = false;
414 }
415
416 boolean isDragTarget() {
417 return mIsDragTarget;
418 }
419
420 void setIsDragOverlapping(boolean isDragOverlapping) {
421 if (mIsDragOverlapping != isDragOverlapping) {
422 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700423 if (mIsDragOverlapping) {
424 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
425 } else {
426 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
427 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700428 invalidate();
429 }
430 }
431
Michael Jurka33945b22010-12-21 18:19:38 -0800432 boolean getIsDragOverlapping() {
433 return mIsDragOverlapping;
434 }
435
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700436 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700437 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700438 if (!mIsDragTarget) {
439 return;
440 }
441
Michael Jurka3e7c7632010-10-02 16:01:03 -0700442 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
443 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
444 // When we're small, we are either drawn normally or in the "accepts drops" state (during
445 // a drag). However, we also drag the mini hover background *over* one of those two
446 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700447 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700448 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700449 }
Romain Guya6abce82009-11-10 02:54:41 -0800450
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700451 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700452 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700453 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700454 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800455 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700456 mTempRect.set(r);
457 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700458 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700459 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700460 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700461 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700462 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800463
Adam Cohen482ed822012-03-02 14:15:13 -0800464 if (DEBUG_VISUALIZE_OCCUPIED) {
465 int[] pt = new int[2];
466 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700467 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800468 for (int i = 0; i < mCountX; i++) {
469 for (int j = 0; j < mCountY; j++) {
470 if (mOccupied[i][j]) {
471 cellToPoint(i, j, pt);
472 canvas.save();
473 canvas.translate(pt[0], pt[1]);
474 cd.draw(canvas);
475 canvas.restore();
476 }
477 }
478 }
479 }
480
Andrew Flynn850d2e72012-04-26 16:51:20 -0700481 int previewOffset = FolderRingAnimator.sPreviewSize;
482
Adam Cohen69ce2e52011-07-03 19:25:21 -0700483 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700484 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700485 for (int i = 0; i < mFolderOuterRings.size(); i++) {
486 FolderRingAnimator fra = mFolderOuterRings.get(i);
487
Adam Cohen5108bc02013-09-20 17:04:51 -0700488 Drawable d;
489 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700490 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700491 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700492
Winson Chung89f97052013-09-20 11:32:26 -0700493 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700494 int centerX = mTempLocation[0] + mCellWidth / 2;
495 int centerY = mTempLocation[1] + previewOffset / 2 +
496 child.getPaddingTop() + grid.folderBackgroundOffset;
497
Adam Cohen5108bc02013-09-20 17:04:51 -0700498 // Draw outer ring, if it exists
499 if (FolderIcon.HAS_OUTER_RING) {
500 d = FolderRingAnimator.sSharedOuterRingDrawable;
501 width = (int) (fra.getOuterRingSize() * getChildrenScale());
502 height = width;
503 canvas.save();
504 canvas.translate(centerX - width / 2, centerY - height / 2);
505 d.setBounds(0, 0, width, height);
506 d.draw(canvas);
507 canvas.restore();
508 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700509
Winson Chung89f97052013-09-20 11:32:26 -0700510 // Draw inner ring
511 d = FolderRingAnimator.sSharedInnerRingDrawable;
512 width = (int) (fra.getInnerRingSize() * getChildrenScale());
513 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700514 canvas.save();
515 canvas.translate(centerX - width / 2, centerY - width / 2);
516 d.setBounds(0, 0, width, height);
517 d.draw(canvas);
518 canvas.restore();
519 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700520 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700521
522 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
523 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
524 int width = d.getIntrinsicWidth();
525 int height = d.getIntrinsicHeight();
526
527 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700528 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700529 if (child != null) {
530 int centerX = mTempLocation[0] + mCellWidth / 2;
531 int centerY = mTempLocation[1] + previewOffset / 2 +
532 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700533
Winson Chung89f97052013-09-20 11:32:26 -0700534 canvas.save();
535 canvas.translate(centerX - width / 2, centerY - width / 2);
536 d.setBounds(0, 0, width, height);
537 d.draw(canvas);
538 canvas.restore();
539 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700540 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700541 }
542
Adam Cohenb5ba0972011-09-07 18:02:31 -0700543 @Override
544 protected void dispatchDraw(Canvas canvas) {
545 super.dispatchDraw(canvas);
546 if (mForegroundAlpha > 0) {
Adam Cohenb5ba0972011-09-07 18:02:31 -0700547 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700548 }
549 }
550
Adam Cohen69ce2e52011-07-03 19:25:21 -0700551 public void showFolderAccept(FolderRingAnimator fra) {
552 mFolderOuterRings.add(fra);
553 }
554
555 public void hideFolderAccept(FolderRingAnimator fra) {
556 if (mFolderOuterRings.contains(fra)) {
557 mFolderOuterRings.remove(fra);
558 }
559 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700560 }
561
Adam Cohenc51934b2011-07-26 21:07:43 -0700562 public void setFolderLeaveBehindCell(int x, int y) {
563 mFolderLeaveBehindCell[0] = x;
564 mFolderLeaveBehindCell[1] = y;
565 invalidate();
566 }
567
568 public void clearFolderLeaveBehind() {
569 mFolderLeaveBehindCell[0] = -1;
570 mFolderLeaveBehindCell[1] = -1;
571 invalidate();
572 }
573
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700574 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700575 public boolean shouldDelayChildPressedState() {
576 return false;
577 }
578
Adam Cohen1462de32012-07-24 22:34:36 -0700579 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700580 try {
581 dispatchRestoreInstanceState(states);
582 } catch (IllegalArgumentException ex) {
583 if (LauncherAppState.isDogfoodBuild()) {
584 throw ex;
585 }
586 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
587 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
588 }
Adam Cohen1462de32012-07-24 22:34:36 -0700589 }
590
Michael Jurkae6235dd2011-10-04 15:02:05 -0700591 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700592 public void cancelLongPress() {
593 super.cancelLongPress();
594
595 // Cancel long press for all children
596 final int count = getChildCount();
597 for (int i = 0; i < count; i++) {
598 final View child = getChildAt(i);
599 child.cancelLongPress();
600 }
601 }
602
Michael Jurkadee05892010-07-27 10:01:56 -0700603 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
604 mInterceptTouchListener = listener;
605 }
606
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800607 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700608 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800609 }
610
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800611 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700612 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800613 }
614
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800615 public void setIsHotseat(boolean isHotseat) {
616 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700617 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800618 }
619
Sunny Goyale9b651e2015-04-24 11:44:51 -0700620 public boolean isHotseat() {
621 return mIsHotseat;
622 }
623
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800624 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700625 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700626 final LayoutParams lp = params;
627
Andrew Flynnde38e422012-05-08 11:22:15 -0700628 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800629 if (child instanceof BubbleTextView) {
630 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700631 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800632 }
633
Adam Cohen307fe232012-08-16 17:55:58 -0700634 child.setScaleX(getChildrenScale());
635 child.setScaleY(getChildrenScale());
636
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800637 // Generate an id for each view, this assumes we have at most 256x256 cells
638 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700639 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700640 // If the horizontal or vertical span is set to -1, it is taken to
641 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700642 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
643 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800644
Winson Chungaafa03c2010-06-11 17:34:16 -0700645 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700646 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700647
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700648 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700649
Winson Chungaafa03c2010-06-11 17:34:16 -0700650 return true;
651 }
652 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800653 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700654
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800655 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700656 public void removeAllViews() {
657 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700658 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700659 }
660
661 @Override
662 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700663 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700664 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700665 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700666 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700667 }
668
669 @Override
670 public void removeView(View view) {
671 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700672 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700673 }
674
675 @Override
676 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700677 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
678 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
680
681 @Override
682 public void removeViewInLayout(View view) {
683 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700684 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700685 }
686
687 @Override
688 public void removeViews(int start, int count) {
689 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700690 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700691 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700692 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700693 }
694
695 @Override
696 public void removeViewsInLayout(int start, int count) {
697 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700698 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700699 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700700 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800701 }
702
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700703 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700704 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800705 * @param x X coordinate of the point
706 * @param y Y coordinate of the point
707 * @param result Array of 2 ints to hold the x and y coordinate of the cell
708 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700709 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700710 final int hStartPadding = getPaddingLeft();
711 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800712
713 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
714 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
715
Adam Cohend22015c2010-07-26 22:02:18 -0700716 final int xAxis = mCountX;
717 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800718
719 if (result[0] < 0) result[0] = 0;
720 if (result[0] >= xAxis) result[0] = xAxis - 1;
721 if (result[1] < 0) result[1] = 0;
722 if (result[1] >= yAxis) result[1] = yAxis - 1;
723 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700724
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800725 /**
726 * Given a point, return the cell that most closely encloses that point
727 * @param x X coordinate of the point
728 * @param y Y coordinate of the point
729 * @param result Array of 2 ints to hold the x and y coordinate of the cell
730 */
731 void pointToCellRounded(int x, int y, int[] result) {
732 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
733 }
734
735 /**
736 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700737 *
738 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800739 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700740 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800741 * @param result Array of 2 ints to hold the x and y coordinate of the point
742 */
743 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700744 final int hStartPadding = getPaddingLeft();
745 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800746
747 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
748 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
749 }
750
Adam Cohene3e27a82011-04-15 12:07:39 -0700751 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800752 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700753 *
754 * @param cellX X coordinate of the cell
755 * @param cellY Y coordinate of the cell
756 *
757 * @param result Array of 2 ints to hold the x and y coordinate of the point
758 */
759 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700760 regionToCenterPoint(cellX, cellY, 1, 1, result);
761 }
762
763 /**
764 * Given a cell coordinate and span return the point that represents the center of the regio
765 *
766 * @param cellX X coordinate of the cell
767 * @param cellY Y coordinate of the cell
768 *
769 * @param result Array of 2 ints to hold the x and y coordinate of the point
770 */
771 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700772 final int hStartPadding = getPaddingLeft();
773 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700774 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
775 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
776 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
777 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700778 }
779
Adam Cohen19f37922012-03-21 11:59:11 -0700780 /**
781 * Given a cell coordinate and span fills out a corresponding pixel rect
782 *
783 * @param cellX X coordinate of the cell
784 * @param cellY Y coordinate of the cell
785 * @param result Rect in which to write the result
786 */
787 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
788 final int hStartPadding = getPaddingLeft();
789 final int vStartPadding = getPaddingTop();
790 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
791 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
792 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
793 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
794 }
795
Adam Cohen482ed822012-03-02 14:15:13 -0800796 public float getDistanceFromCell(float x, float y, int[] cell) {
797 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700798 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800799 }
800
Romain Guy84f296c2009-11-04 15:00:44 -0800801 int getCellWidth() {
802 return mCellWidth;
803 }
804
805 int getCellHeight() {
806 return mCellHeight;
807 }
808
Adam Cohend4844c32011-02-18 19:25:06 -0800809 int getWidthGap() {
810 return mWidthGap;
811 }
812
813 int getHeightGap() {
814 return mHeightGap;
815 }
816
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700817 public void setFixedSize(int width, int height) {
818 mFixedWidth = width;
819 mFixedHeight = height;
820 }
821
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800822 @Override
823 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800824 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800825 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700826 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
827 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700828 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
829 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700830 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700831 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
832 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700833 if (cw != mCellWidth || ch != mCellHeight) {
834 mCellWidth = cw;
835 mCellHeight = ch;
836 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
837 mHeightGap, mCountX, mCountY);
838 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700839 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700840
Winson Chung2d75f122013-09-23 16:53:31 -0700841 int newWidth = childWidthSize;
842 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700843 if (mFixedWidth > 0 && mFixedHeight > 0) {
844 newWidth = mFixedWidth;
845 newHeight = mFixedHeight;
846 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800847 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
848 }
849
Adam Cohend22015c2010-07-26 22:02:18 -0700850 int numWidthGaps = mCountX - 1;
851 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800852
Adam Cohen234c4cd2011-07-17 21:03:04 -0700853 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700854 int hSpace = childWidthSize;
855 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700856 int hFreeSpace = hSpace - (mCountX * mCellWidth);
857 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700858 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
859 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700860 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
861 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700862 } else {
863 mWidthGap = mOriginalWidthGap;
864 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700865 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700866
867 // Make the feedback view large enough to hold the blur bitmap.
868 mTouchFeedbackView.measure(
869 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
870 MeasureSpec.EXACTLY),
871 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
872 MeasureSpec.EXACTLY));
873
874 mShortcutsAndWidgets.measure(
875 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
876 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
877
878 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
879 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700880 if (mFixedWidth > 0 && mFixedHeight > 0) {
881 setMeasuredDimension(maxWidth, maxHeight);
882 } else {
883 setMeasuredDimension(widthSize, heightSize);
884 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800885 }
886
887 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700888 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700889 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
890 (mCountX * mCellWidth);
891 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
892 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700893
894 mTouchFeedbackView.layout(left, top,
895 left + mTouchFeedbackView.getMeasuredWidth(),
896 top + mTouchFeedbackView.getMeasuredHeight());
897 mShortcutsAndWidgets.layout(left, top,
898 left + r - l,
899 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800900 }
901
902 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700903 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
904 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700905
906 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700907 mBackground.getPadding(mTempRect);
908 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
909 w + mTempRect.right, h + mTempRect.bottom);
Winson Chung82a9bd22013-10-08 16:02:34 -0700910
Sunny Goyal2805e632015-05-20 15:35:32 -0700911 mOverScrollLeft.setBounds(0, 0, w, h);
912 mOverScrollRight.setBounds(0, 0, w, h);
Michael Jurkadee05892010-07-27 10:01:56 -0700913 }
914
915 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800916 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700917 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800918 }
919
920 @Override
921 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700922 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800923 }
924
Michael Jurka5f1c5092010-09-03 14:15:02 -0700925 public float getBackgroundAlpha() {
926 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700927 }
928
Michael Jurka5f1c5092010-09-03 14:15:02 -0700929 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800930 if (mBackgroundAlpha != alpha) {
931 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700932 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800933 }
Michael Jurkadee05892010-07-27 10:01:56 -0700934 }
935
Sunny Goyal2805e632015-05-20 15:35:32 -0700936 @Override
937 protected boolean verifyDrawable(Drawable who) {
938 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
939 }
940
Michael Jurkaa52570f2012-03-20 03:18:20 -0700941 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700942 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700943 }
944
Michael Jurkaa52570f2012-03-20 03:18:20 -0700945 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700946 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700947 }
948
Patrick Dubroy440c3602010-07-13 17:50:32 -0700949 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700950 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700951 }
952
Adam Cohen76fc0852011-06-17 13:26:23 -0700953 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800954 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700955 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800956 boolean[][] occupied = mOccupied;
957 if (!permanent) {
958 occupied = mTmpOccupied;
959 }
960
Adam Cohen19f37922012-03-21 11:59:11 -0700961 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700962 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
963 final ItemInfo info = (ItemInfo) child.getTag();
964
965 // We cancel any existing animations
966 if (mReorderAnimators.containsKey(lp)) {
967 mReorderAnimators.get(lp).cancel();
968 mReorderAnimators.remove(lp);
969 }
970
Adam Cohen482ed822012-03-02 14:15:13 -0800971 final int oldX = lp.x;
972 final int oldY = lp.y;
973 if (adjustOccupied) {
974 occupied[lp.cellX][lp.cellY] = false;
975 occupied[cellX][cellY] = true;
976 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700977 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800978 if (permanent) {
979 lp.cellX = info.cellX = cellX;
980 lp.cellY = info.cellY = cellY;
981 } else {
982 lp.tmpCellX = cellX;
983 lp.tmpCellY = cellY;
984 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700985 clc.setupLp(lp);
986 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800987 final int newX = lp.x;
988 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700989
Adam Cohen76fc0852011-06-17 13:26:23 -0700990 lp.x = oldX;
991 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700992
Adam Cohen482ed822012-03-02 14:15:13 -0800993 // Exit early if we're not actually moving the view
994 if (oldX == newX && oldY == newY) {
995 lp.isLockedToGrid = true;
996 return true;
997 }
998
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100999 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001000 va.setDuration(duration);
1001 mReorderAnimators.put(lp, va);
1002
1003 va.addUpdateListener(new AnimatorUpdateListener() {
1004 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001005 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001006 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001007 lp.x = (int) ((1 - r) * oldX + r * newX);
1008 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001009 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001010 }
1011 });
Adam Cohen482ed822012-03-02 14:15:13 -08001012 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001013 boolean cancelled = false;
1014 public void onAnimationEnd(Animator animation) {
1015 // If the animation was cancelled, it means that another animation
1016 // has interrupted this one, and we don't want to lock the item into
1017 // place just yet.
1018 if (!cancelled) {
1019 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001020 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001021 }
1022 if (mReorderAnimators.containsKey(lp)) {
1023 mReorderAnimators.remove(lp);
1024 }
1025 }
1026 public void onAnimationCancel(Animator animation) {
1027 cancelled = true;
1028 }
1029 });
Adam Cohen482ed822012-03-02 14:15:13 -08001030 va.setStartDelay(delay);
1031 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001032 return true;
1033 }
1034 return false;
1035 }
1036
Adam Cohen482ed822012-03-02 14:15:13 -08001037 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1038 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001039 final int oldDragCellX = mDragCell[0];
1040 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001041
Adam Cohen2801caf2011-05-13 20:57:39 -07001042 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001043 return;
1044 }
1045
Adam Cohen482ed822012-03-02 14:15:13 -08001046 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1047 mDragCell[0] = cellX;
1048 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001049 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001050 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001051 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001052
Joe Onorato4be866d2010-10-10 11:26:02 -07001053 int left = topLeft[0];
1054 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001055
Winson Chungb8c69f32011-10-19 21:36:08 -07001056 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001057 // When drawing the drag outline, it did not account for margin offsets
1058 // added by the view's parent.
1059 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1060 left += lp.leftMargin;
1061 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001062
Adam Cohen99e8b402011-03-25 19:23:43 -07001063 // Offsets due to the size difference between the View and the dragOutline.
1064 // There is a size difference to account for the outer blur, which may lie
1065 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001066 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001067 // We center about the x axis
1068 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1069 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001070 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001071 if (dragOffset != null && dragRegion != null) {
1072 // Center the drag region *horizontally* in the cell and apply a drag
1073 // outline offset
1074 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1075 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001076 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1077 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1078 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001079 } else {
1080 // Center the drag outline in the cell
1081 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1082 - dragOutline.getWidth()) / 2;
1083 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1084 - dragOutline.getHeight()) / 2;
1085 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001086 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001087 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001088 mDragOutlineAnims[oldIndex].animateOut();
1089 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001090 Rect r = mDragOutlines[mDragOutlineCurrent];
1091 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1092 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001093 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001094 }
Winson Chung150fbab2010-09-29 17:14:26 -07001095
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001096 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1097 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001098 }
1099 }
1100
Adam Cohene0310962011-04-18 16:15:31 -07001101 public void clearDragOutlines() {
1102 final int oldIndex = mDragOutlineCurrent;
1103 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001104 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001105 }
1106
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001107 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001108 * Find a vacant area that will fit the given bounds nearest the requested
1109 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001110 *
Romain Guy51afc022009-05-04 18:03:43 -07001111 * @param pixelX The X location at which you want to search for a vacant area.
1112 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001113 * @param spanX Horizontal span of the object.
1114 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001115 * @param result Array in which to place the result, or null (in which case a new array will
1116 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001117 * @return The X, Y cell of a vacant area that can contain this object,
1118 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001119 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001120 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1121 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001122 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001123
Michael Jurka6a1435d2010-09-27 17:35:12 -07001124 /**
1125 * Find a vacant area that will fit the given bounds nearest the requested
1126 * cell location. Uses Euclidean distance to score multiple vacant areas.
1127 *
1128 * @param pixelX The X location at which you want to search for a vacant area.
1129 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001130 * @param minSpanX The minimum horizontal span required
1131 * @param minSpanY The minimum vertical span required
1132 * @param spanX Horizontal span of the object.
1133 * @param spanY Vertical span of the object.
1134 * @param result Array in which to place the result, or null (in which case a new array will
1135 * be allocated)
1136 * @return The X, Y cell of a vacant area that can contain this object,
1137 * nearest the requested location.
1138 */
1139 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1140 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001141 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001142 result, resultSpan);
1143 }
1144
Adam Cohend41fbf52012-02-16 23:53:59 -08001145 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1146 private void lazyInitTempRectStack() {
1147 if (mTempRectStack.isEmpty()) {
1148 for (int i = 0; i < mCountX * mCountY; i++) {
1149 mTempRectStack.push(new Rect());
1150 }
1151 }
1152 }
Adam Cohen482ed822012-03-02 14:15:13 -08001153
Adam Cohend41fbf52012-02-16 23:53:59 -08001154 private void recycleTempRects(Stack<Rect> used) {
1155 while (!used.isEmpty()) {
1156 mTempRectStack.push(used.pop());
1157 }
1158 }
1159
1160 /**
1161 * Find a vacant area that will fit the given bounds nearest the requested
1162 * cell location. Uses Euclidean distance to score multiple vacant areas.
1163 *
1164 * @param pixelX The X location at which you want to search for a vacant area.
1165 * @param pixelY The Y location at which you want to search for a vacant area.
1166 * @param minSpanX The minimum horizontal span required
1167 * @param minSpanY The minimum vertical span required
1168 * @param spanX Horizontal span of the object.
1169 * @param spanY Vertical span of the object.
1170 * @param ignoreOccupied If true, the result can be an occupied cell
1171 * @param result Array in which to place the result, or null (in which case a new array will
1172 * be allocated)
1173 * @return The X, Y cell of a vacant area that can contain this object,
1174 * nearest the requested location.
1175 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001176 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1177 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001178 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001179
Adam Cohene3e27a82011-04-15 12:07:39 -07001180 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1181 // to the center of the item, but we are searching based on the top-left cell, so
1182 // we translate the point over to correspond to the top-left.
1183 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1184 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1185
Jeff Sharkey70864282009-04-07 21:08:40 -07001186 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001187 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001188 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001189 final Rect bestRect = new Rect(-1, -1, -1, -1);
1190 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001191
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001192 final int countX = mCountX;
1193 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001194
Adam Cohend41fbf52012-02-16 23:53:59 -08001195 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1196 spanX < minSpanX || spanY < minSpanY) {
1197 return bestXY;
1198 }
1199
1200 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001201 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001202 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1203 int ySize = -1;
1204 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001205 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001206 // First, let's see if this thing fits anywhere
1207 for (int i = 0; i < minSpanX; i++) {
1208 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001209 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001210 continue inner;
1211 }
Michael Jurkac28de512010-08-13 11:27:44 -07001212 }
1213 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001214 xSize = minSpanX;
1215 ySize = minSpanY;
1216
1217 // We know that the item will fit at _some_ acceptable size, now let's see
1218 // how big we can make it. We'll alternate between incrementing x and y spans
1219 // until we hit a limit.
1220 boolean incX = true;
1221 boolean hitMaxX = xSize >= spanX;
1222 boolean hitMaxY = ySize >= spanY;
1223 while (!(hitMaxX && hitMaxY)) {
1224 if (incX && !hitMaxX) {
1225 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001226 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001227 // We can't move out horizontally
1228 hitMaxX = true;
1229 }
1230 }
1231 if (!hitMaxX) {
1232 xSize++;
1233 }
1234 } else if (!hitMaxY) {
1235 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001236 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001237 // We can't move out vertically
1238 hitMaxY = true;
1239 }
1240 }
1241 if (!hitMaxY) {
1242 ySize++;
1243 }
1244 }
1245 hitMaxX |= xSize >= spanX;
1246 hitMaxY |= ySize >= spanY;
1247 incX = !incX;
1248 }
1249 incX = true;
1250 hitMaxX = xSize >= spanX;
1251 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001252 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001253 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001254 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001255
Adam Cohend41fbf52012-02-16 23:53:59 -08001256 // We verify that the current rect is not a sub-rect of any of our previous
1257 // candidates. In this case, the current rect is disqualified in favour of the
1258 // containing rect.
1259 Rect currentRect = mTempRectStack.pop();
1260 currentRect.set(x, y, x + xSize, y + ySize);
1261 boolean contained = false;
1262 for (Rect r : validRegions) {
1263 if (r.contains(currentRect)) {
1264 contained = true;
1265 break;
1266 }
1267 }
1268 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001269 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001270
Adam Cohend41fbf52012-02-16 23:53:59 -08001271 if ((distance <= bestDistance && !contained) ||
1272 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001273 bestDistance = distance;
1274 bestXY[0] = x;
1275 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001276 if (resultSpan != null) {
1277 resultSpan[0] = xSize;
1278 resultSpan[1] = ySize;
1279 }
1280 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001281 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001282 }
1283 }
1284
Adam Cohenc0dcf592011-06-01 15:30:43 -07001285 // Return -1, -1 if no suitable location found
1286 if (bestDistance == Double.MAX_VALUE) {
1287 bestXY[0] = -1;
1288 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001289 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001290 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001291 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001292 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001293
Adam Cohen482ed822012-03-02 14:15:13 -08001294 /**
1295 * Find a vacant area that will fit the given bounds nearest the requested
1296 * cell location, and will also weigh in a suggested direction vector of the
1297 * desired location. This method computers distance based on unit grid distances,
1298 * not pixel distances.
1299 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001300 * @param cellX The X cell nearest to which you want to search for a vacant area.
1301 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001302 * @param spanX Horizontal span of the object.
1303 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001304 * @param direction The favored direction in which the views should move from x, y
1305 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1306 * matches exactly. Otherwise we find the best matching direction.
1307 * @param occoupied The array which represents which cells in the CellLayout are occupied
1308 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001309 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001310 * @param result Array in which to place the result, or null (in which case a new array will
1311 * be allocated)
1312 * @return The X, Y cell of a vacant area that can contain this object,
1313 * nearest the requested location.
1314 */
1315 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001316 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001317 // Keep track of best-scoring drop area
1318 final int[] bestXY = result != null ? result : new int[2];
1319 float bestDistance = Float.MAX_VALUE;
1320 int bestDirectionScore = Integer.MIN_VALUE;
1321
1322 final int countX = mCountX;
1323 final int countY = mCountY;
1324
1325 for (int y = 0; y < countY - (spanY - 1); y++) {
1326 inner:
1327 for (int x = 0; x < countX - (spanX - 1); x++) {
1328 // First, let's see if this thing fits anywhere
1329 for (int i = 0; i < spanX; i++) {
1330 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001331 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001332 continue inner;
1333 }
1334 }
1335 }
1336
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001337 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001338 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001339 computeDirectionVector(x - cellX, y - cellY, curDirection);
1340 // The direction score is just the dot product of the two candidate direction
1341 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001342 int curDirectionScore = direction[0] * curDirection[0] +
1343 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001344 boolean exactDirectionOnly = false;
1345 boolean directionMatches = direction[0] == curDirection[0] &&
1346 direction[0] == curDirection[0];
1347 if ((directionMatches || !exactDirectionOnly) &&
1348 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001349 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1350 bestDistance = distance;
1351 bestDirectionScore = curDirectionScore;
1352 bestXY[0] = x;
1353 bestXY[1] = y;
1354 }
1355 }
1356 }
1357
1358 // Return -1, -1 if no suitable location found
1359 if (bestDistance == Float.MAX_VALUE) {
1360 bestXY[0] = -1;
1361 bestXY[1] = -1;
1362 }
1363 return bestXY;
1364 }
1365
1366 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001367 int[] direction, ItemConfiguration currentState) {
1368 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001369 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001370 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001371 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1372
Adam Cohen8baab352012-03-20 17:39:21 -07001373 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001374
1375 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001376 c.x = mTempLocation[0];
1377 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001378 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001379 }
Adam Cohen8baab352012-03-20 17:39:21 -07001380 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001381 return success;
1382 }
1383
Adam Cohenf3900c22012-11-16 18:28:11 -08001384 /**
1385 * This helper class defines a cluster of views. It helps with defining complex edges
1386 * of the cluster and determining how those edges interact with other views. The edges
1387 * essentially define a fine-grained boundary around the cluster of views -- like a more
1388 * precise version of a bounding box.
1389 */
1390 private class ViewCluster {
1391 final static int LEFT = 0;
1392 final static int TOP = 1;
1393 final static int RIGHT = 2;
1394 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001395
Adam Cohenf3900c22012-11-16 18:28:11 -08001396 ArrayList<View> views;
1397 ItemConfiguration config;
1398 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001399
Adam Cohenf3900c22012-11-16 18:28:11 -08001400 int[] leftEdge = new int[mCountY];
1401 int[] rightEdge = new int[mCountY];
1402 int[] topEdge = new int[mCountX];
1403 int[] bottomEdge = new int[mCountX];
1404 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1405
1406 @SuppressWarnings("unchecked")
1407 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1408 this.views = (ArrayList<View>) views.clone();
1409 this.config = config;
1410 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001411 }
1412
Adam Cohenf3900c22012-11-16 18:28:11 -08001413 void resetEdges() {
1414 for (int i = 0; i < mCountX; i++) {
1415 topEdge[i] = -1;
1416 bottomEdge[i] = -1;
1417 }
1418 for (int i = 0; i < mCountY; i++) {
1419 leftEdge[i] = -1;
1420 rightEdge[i] = -1;
1421 }
1422 leftEdgeDirty = true;
1423 rightEdgeDirty = true;
1424 bottomEdgeDirty = true;
1425 topEdgeDirty = true;
1426 boundingRectDirty = true;
1427 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001428
Adam Cohenf3900c22012-11-16 18:28:11 -08001429 void computeEdge(int which, int[] edge) {
1430 int count = views.size();
1431 for (int i = 0; i < count; i++) {
1432 CellAndSpan cs = config.map.get(views.get(i));
1433 switch (which) {
1434 case LEFT:
1435 int left = cs.x;
1436 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1437 if (left < edge[j] || edge[j] < 0) {
1438 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001439 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001440 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001441 break;
1442 case RIGHT:
1443 int right = cs.x + cs.spanX;
1444 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1445 if (right > edge[j]) {
1446 edge[j] = right;
1447 }
1448 }
1449 break;
1450 case TOP:
1451 int top = cs.y;
1452 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1453 if (top < edge[j] || edge[j] < 0) {
1454 edge[j] = top;
1455 }
1456 }
1457 break;
1458 case BOTTOM:
1459 int bottom = cs.y + cs.spanY;
1460 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1461 if (bottom > edge[j]) {
1462 edge[j] = bottom;
1463 }
1464 }
1465 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001466 }
1467 }
1468 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001469
1470 boolean isViewTouchingEdge(View v, int whichEdge) {
1471 CellAndSpan cs = config.map.get(v);
1472
1473 int[] edge = getEdge(whichEdge);
1474
1475 switch (whichEdge) {
1476 case LEFT:
1477 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1478 if (edge[i] == cs.x + cs.spanX) {
1479 return true;
1480 }
1481 }
1482 break;
1483 case RIGHT:
1484 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1485 if (edge[i] == cs.x) {
1486 return true;
1487 }
1488 }
1489 break;
1490 case TOP:
1491 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1492 if (edge[i] == cs.y + cs.spanY) {
1493 return true;
1494 }
1495 }
1496 break;
1497 case BOTTOM:
1498 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1499 if (edge[i] == cs.y) {
1500 return true;
1501 }
1502 }
1503 break;
1504 }
1505 return false;
1506 }
1507
1508 void shift(int whichEdge, int delta) {
1509 for (View v: views) {
1510 CellAndSpan c = config.map.get(v);
1511 switch (whichEdge) {
1512 case LEFT:
1513 c.x -= delta;
1514 break;
1515 case RIGHT:
1516 c.x += delta;
1517 break;
1518 case TOP:
1519 c.y -= delta;
1520 break;
1521 case BOTTOM:
1522 default:
1523 c.y += delta;
1524 break;
1525 }
1526 }
1527 resetEdges();
1528 }
1529
1530 public void addView(View v) {
1531 views.add(v);
1532 resetEdges();
1533 }
1534
1535 public Rect getBoundingRect() {
1536 if (boundingRectDirty) {
1537 boolean first = true;
1538 for (View v: views) {
1539 CellAndSpan c = config.map.get(v);
1540 if (first) {
1541 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1542 first = false;
1543 } else {
1544 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1545 }
1546 }
1547 }
1548 return boundingRect;
1549 }
1550
1551 public int[] getEdge(int which) {
1552 switch (which) {
1553 case LEFT:
1554 return getLeftEdge();
1555 case RIGHT:
1556 return getRightEdge();
1557 case TOP:
1558 return getTopEdge();
1559 case BOTTOM:
1560 default:
1561 return getBottomEdge();
1562 }
1563 }
1564
1565 public int[] getLeftEdge() {
1566 if (leftEdgeDirty) {
1567 computeEdge(LEFT, leftEdge);
1568 }
1569 return leftEdge;
1570 }
1571
1572 public int[] getRightEdge() {
1573 if (rightEdgeDirty) {
1574 computeEdge(RIGHT, rightEdge);
1575 }
1576 return rightEdge;
1577 }
1578
1579 public int[] getTopEdge() {
1580 if (topEdgeDirty) {
1581 computeEdge(TOP, topEdge);
1582 }
1583 return topEdge;
1584 }
1585
1586 public int[] getBottomEdge() {
1587 if (bottomEdgeDirty) {
1588 computeEdge(BOTTOM, bottomEdge);
1589 }
1590 return bottomEdge;
1591 }
1592
1593 PositionComparator comparator = new PositionComparator();
1594 class PositionComparator implements Comparator<View> {
1595 int whichEdge = 0;
1596 public int compare(View left, View right) {
1597 CellAndSpan l = config.map.get(left);
1598 CellAndSpan r = config.map.get(right);
1599 switch (whichEdge) {
1600 case LEFT:
1601 return (r.x + r.spanX) - (l.x + l.spanX);
1602 case RIGHT:
1603 return l.x - r.x;
1604 case TOP:
1605 return (r.y + r.spanY) - (l.y + l.spanY);
1606 case BOTTOM:
1607 default:
1608 return l.y - r.y;
1609 }
1610 }
1611 }
1612
1613 public void sortConfigurationForEdgePush(int edge) {
1614 comparator.whichEdge = edge;
1615 Collections.sort(config.sortedViews, comparator);
1616 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001617 }
1618
Adam Cohenf3900c22012-11-16 18:28:11 -08001619 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1620 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001621
Adam Cohenf3900c22012-11-16 18:28:11 -08001622 ViewCluster cluster = new ViewCluster(views, currentState);
1623 Rect clusterRect = cluster.getBoundingRect();
1624 int whichEdge;
1625 int pushDistance;
1626 boolean fail = false;
1627
1628 // Determine the edge of the cluster that will be leading the push and how far
1629 // the cluster must be shifted.
1630 if (direction[0] < 0) {
1631 whichEdge = ViewCluster.LEFT;
1632 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001633 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001634 whichEdge = ViewCluster.RIGHT;
1635 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1636 } else if (direction[1] < 0) {
1637 whichEdge = ViewCluster.TOP;
1638 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1639 } else {
1640 whichEdge = ViewCluster.BOTTOM;
1641 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001642 }
1643
Adam Cohenf3900c22012-11-16 18:28:11 -08001644 // Break early for invalid push distance.
1645 if (pushDistance <= 0) {
1646 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001647 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001648
1649 // Mark the occupied state as false for the group of views we want to move.
1650 for (View v: views) {
1651 CellAndSpan c = currentState.map.get(v);
1652 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1653 }
1654
1655 // We save the current configuration -- if we fail to find a solution we will revert
1656 // to the initial state. The process of finding a solution modifies the configuration
1657 // in place, hence the need for revert in the failure case.
1658 currentState.save();
1659
1660 // The pushing algorithm is simplified by considering the views in the order in which
1661 // they would be pushed by the cluster. For example, if the cluster is leading with its
1662 // left edge, we consider sort the views by their right edge, from right to left.
1663 cluster.sortConfigurationForEdgePush(whichEdge);
1664
1665 while (pushDistance > 0 && !fail) {
1666 for (View v: currentState.sortedViews) {
1667 // For each view that isn't in the cluster, we see if the leading edge of the
1668 // cluster is contacting the edge of that view. If so, we add that view to the
1669 // cluster.
1670 if (!cluster.views.contains(v) && v != dragView) {
1671 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1672 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1673 if (!lp.canReorder) {
1674 // The push solution includes the all apps button, this is not viable.
1675 fail = true;
1676 break;
1677 }
1678 cluster.addView(v);
1679 CellAndSpan c = currentState.map.get(v);
1680
1681 // Adding view to cluster, mark it as not occupied.
1682 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1683 }
1684 }
1685 }
1686 pushDistance--;
1687
1688 // The cluster has been completed, now we move the whole thing over in the appropriate
1689 // direction.
1690 cluster.shift(whichEdge, 1);
1691 }
1692
1693 boolean foundSolution = false;
1694 clusterRect = cluster.getBoundingRect();
1695
1696 // Due to the nature of the algorithm, the only check required to verify a valid solution
1697 // is to ensure that completed shifted cluster lies completely within the cell layout.
1698 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1699 clusterRect.bottom <= mCountY) {
1700 foundSolution = true;
1701 } else {
1702 currentState.restore();
1703 }
1704
1705 // In either case, we set the occupied array as marked for the location of the views
1706 for (View v: cluster.views) {
1707 CellAndSpan c = currentState.map.get(v);
1708 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1709 }
1710
1711 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001712 }
1713
Adam Cohen482ed822012-03-02 14:15:13 -08001714 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001715 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001716 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001717
Adam Cohen8baab352012-03-20 17:39:21 -07001718 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001719 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001720 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001721 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001722 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001723 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001724 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001725 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001726 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001727 }
1728 }
Adam Cohen8baab352012-03-20 17:39:21 -07001729
Adam Cohen8baab352012-03-20 17:39:21 -07001730 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001731 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001732 CellAndSpan c = currentState.map.get(v);
1733 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1734 }
1735
Adam Cohen47a876d2012-03-19 13:21:41 -07001736 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1737 int top = boundingRect.top;
1738 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001739 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001740 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001741 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001742 CellAndSpan c = currentState.map.get(v);
1743 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001744 }
1745
Adam Cohen482ed822012-03-02 14:15:13 -08001746 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1747
Adam Cohenf3900c22012-11-16 18:28:11 -08001748 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1749 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001750
Adam Cohen8baab352012-03-20 17:39:21 -07001751 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001752 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001753 int deltaX = mTempLocation[0] - boundingRect.left;
1754 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001755 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001756 CellAndSpan c = currentState.map.get(v);
1757 c.x += deltaX;
1758 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001759 }
1760 success = true;
1761 }
Adam Cohen8baab352012-03-20 17:39:21 -07001762
1763 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001764 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001765 CellAndSpan c = currentState.map.get(v);
1766 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001767 }
1768 return success;
1769 }
1770
1771 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1772 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1773 }
1774
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001775 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1776 // to push items in each of the cardinal directions, in an order based on the direction vector
1777 // passed.
1778 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1779 int[] direction, View ignoreView, ItemConfiguration solution) {
1780 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001781 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001782 // separately in each of the components.
1783 int temp = direction[1];
1784 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001785
1786 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001787 ignoreView, solution)) {
1788 return true;
1789 }
1790 direction[1] = temp;
1791 temp = direction[0];
1792 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001793
1794 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
1801 // Now we try pushing in each component of the opposite direction
1802 direction[0] *= -1;
1803 direction[1] *= -1;
1804 temp = direction[1];
1805 direction[1] = 0;
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 }
1810
1811 direction[1] = temp;
1812 temp = direction[0];
1813 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001814 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001815 ignoreView, solution)) {
1816 return true;
1817 }
1818 // revert the direction
1819 direction[0] = temp;
1820 direction[0] *= -1;
1821 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001822
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001823 } else {
1824 // If the direction vector has a single non-zero component, we push first in the
1825 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001826 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001827 ignoreView, solution)) {
1828 return true;
1829 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001830 // Then we try the opposite direction
1831 direction[0] *= -1;
1832 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001833 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001834 ignoreView, solution)) {
1835 return true;
1836 }
1837 // Switch the direction back
1838 direction[0] *= -1;
1839 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001840
1841 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001842 // to find a solution by pushing along the perpendicular axis.
1843
1844 // Swap the components
1845 int temp = direction[1];
1846 direction[1] = direction[0];
1847 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001848 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001849 ignoreView, solution)) {
1850 return true;
1851 }
1852
1853 // Then we try the opposite direction
1854 direction[0] *= -1;
1855 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001856 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001857 ignoreView, solution)) {
1858 return true;
1859 }
1860 // Switch the direction back
1861 direction[0] *= -1;
1862 direction[1] *= -1;
1863
1864 // Swap the components back
1865 temp = direction[1];
1866 direction[1] = direction[0];
1867 direction[0] = temp;
1868 }
1869 return false;
1870 }
1871
Adam Cohen482ed822012-03-02 14:15:13 -08001872 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001873 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001874 // Return early if get invalid cell positions
1875 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001876
Adam Cohen8baab352012-03-20 17:39:21 -07001877 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001878 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001879
Adam Cohen8baab352012-03-20 17:39:21 -07001880 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001881 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001882 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001883 if (c != null) {
1884 c.x = cellX;
1885 c.y = cellY;
1886 }
Adam Cohen482ed822012-03-02 14:15:13 -08001887 }
Adam Cohen482ed822012-03-02 14:15:13 -08001888 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1889 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001890 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001891 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001892 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001893 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001894 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001895 if (Rect.intersects(r0, r1)) {
1896 if (!lp.canReorder) {
1897 return false;
1898 }
1899 mIntersectingViews.add(child);
1900 }
1901 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001902
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001903 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1904
Winson Chung5f8afe62013-08-12 16:19:28 -07001905 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001906 // we try to find a solution such that no displaced item travels through another item
1907 // without also displacing that item.
1908 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001909 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001910 return true;
1911 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001912
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001913 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001914 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001915 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001916 return true;
1917 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001918
Adam Cohen482ed822012-03-02 14:15:13 -08001919 // Ok, they couldn't move as a block, let's move them individually
1920 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001921 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001922 return false;
1923 }
1924 }
1925 return true;
1926 }
1927
1928 /*
1929 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1930 * the provided point and the provided cell
1931 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001932 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001933 double angle = Math.atan(((float) deltaY) / deltaX);
1934
1935 result[0] = 0;
1936 result[1] = 0;
1937 if (Math.abs(Math.cos(angle)) > 0.5f) {
1938 result[0] = (int) Math.signum(deltaX);
1939 }
1940 if (Math.abs(Math.sin(angle)) > 0.5f) {
1941 result[1] = (int) Math.signum(deltaY);
1942 }
1943 }
1944
Adam Cohen8baab352012-03-20 17:39:21 -07001945 private void copyOccupiedArray(boolean[][] occupied) {
1946 for (int i = 0; i < mCountX; i++) {
1947 for (int j = 0; j < mCountY; j++) {
1948 occupied[i][j] = mOccupied[i][j];
1949 }
1950 }
1951 }
1952
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001953 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001954 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1955 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001956 // Copy the current state into the solution. This solution will be manipulated as necessary.
1957 copyCurrentStateToSolution(solution, false);
1958 // Copy the current occupied array into the temporary occupied array. This array will be
1959 // manipulated as necessary to find a solution.
1960 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001961
1962 // We find the nearest cell into which we would place the dragged item, assuming there's
1963 // nothing in its way.
1964 int result[] = new int[2];
1965 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1966
1967 boolean success = false;
1968 // First we try the exact nearest position of the item being dragged,
1969 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001970 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1971 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001972
1973 if (!success) {
1974 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1975 // x, then 1 in y etc.
1976 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001977 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1978 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001979 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001980 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1981 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001982 }
1983 solution.isSolution = false;
1984 } else {
1985 solution.isSolution = true;
1986 solution.dragViewX = result[0];
1987 solution.dragViewY = result[1];
1988 solution.dragViewSpanX = spanX;
1989 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001990 }
1991 return solution;
1992 }
1993
1994 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001995 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001996 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001997 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001998 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001999 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002000 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002001 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002002 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002003 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002004 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002005 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002006 }
2007 }
2008
2009 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2010 for (int i = 0; i < mCountX; i++) {
2011 for (int j = 0; j < mCountY; j++) {
2012 mTmpOccupied[i][j] = false;
2013 }
2014 }
2015
Michael Jurkaa52570f2012-03-20 03:18:20 -07002016 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002017 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002018 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002019 if (child == dragView) continue;
2020 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002021 CellAndSpan c = solution.map.get(child);
2022 if (c != null) {
2023 lp.tmpCellX = c.x;
2024 lp.tmpCellY = c.y;
2025 lp.cellHSpan = c.spanX;
2026 lp.cellVSpan = c.spanY;
2027 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002028 }
2029 }
2030 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2031 solution.dragViewSpanY, mTmpOccupied, true);
2032 }
2033
2034 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2035 commitDragView) {
2036
2037 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2038 for (int i = 0; i < mCountX; i++) {
2039 for (int j = 0; j < mCountY; j++) {
2040 occupied[i][j] = false;
2041 }
2042 }
2043
Michael Jurkaa52570f2012-03-20 03:18:20 -07002044 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002045 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002046 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002047 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002048 CellAndSpan c = solution.map.get(child);
2049 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002050 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2051 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002052 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002053 }
2054 }
2055 if (commitDragView) {
2056 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2057 solution.dragViewSpanY, occupied, true);
2058 }
2059 }
2060
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002061
2062 // This method starts or changes the reorder preview animations
2063 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2064 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002065 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002066 for (int i = 0; i < childCount; i++) {
2067 View child = mShortcutsAndWidgets.getChildAt(i);
2068 if (child == dragView) continue;
2069 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002070 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2071 != null && !solution.intersectingViews.contains(child);
2072
Adam Cohen19f37922012-03-21 11:59:11 -07002073 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002074 if (c != null && !skip) {
2075 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2076 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002077 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002078 }
2079 }
2080 }
2081
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002082 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002083 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002084 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002085 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002086 float finalDeltaX;
2087 float finalDeltaY;
2088 float initDeltaX;
2089 float initDeltaY;
2090 float finalScale;
2091 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002092 int mode;
2093 boolean repeating = false;
2094 private static final int PREVIEW_DURATION = 300;
2095 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2096
2097 public static final int MODE_HINT = 0;
2098 public static final int MODE_PREVIEW = 1;
2099
Adam Cohene7587d22012-05-24 18:50:02 -07002100 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002101
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002102 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2103 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002104 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2105 final int x0 = mTmpPoint[0];
2106 final int y0 = mTmpPoint[1];
2107 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2108 final int x1 = mTmpPoint[0];
2109 final int y1 = mTmpPoint[1];
2110 final int dX = x1 - x0;
2111 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002112 finalDeltaX = 0;
2113 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002114 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002115 if (dX == dY && dX == 0) {
2116 } else {
2117 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002118 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002119 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002120 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002121 } else {
2122 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002123 finalDeltaX = (int) (- dir * Math.signum(dX) *
2124 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2125 finalDeltaY = (int) (- dir * Math.signum(dY) *
2126 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002127 }
2128 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002129 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002130 initDeltaX = child.getTranslationX();
2131 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002132 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002133 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002134 this.child = child;
2135 }
2136
Adam Cohend024f982012-05-23 18:26:45 -07002137 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002138 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002139 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002140 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002141 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002142 if (finalDeltaX == 0 && finalDeltaY == 0) {
2143 completeAnimationImmediately();
2144 return;
2145 }
Adam Cohen19f37922012-03-21 11:59:11 -07002146 }
Adam Cohend024f982012-05-23 18:26:45 -07002147 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002148 return;
2149 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002150 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002151 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002152 va.setRepeatMode(ValueAnimator.REVERSE);
2153 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002154 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002155 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002156 va.addUpdateListener(new AnimatorUpdateListener() {
2157 @Override
2158 public void onAnimationUpdate(ValueAnimator animation) {
2159 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002160 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2161 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2162 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002163 child.setTranslationX(x);
2164 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002165 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002166 child.setScaleX(s);
2167 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002168 }
2169 });
2170 va.addListener(new AnimatorListenerAdapter() {
2171 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002172 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002173 initDeltaX = 0;
2174 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002175 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002176 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002177 }
2178 });
Adam Cohen19f37922012-03-21 11:59:11 -07002179 mShakeAnimators.put(child, this);
2180 va.start();
2181 }
2182
Adam Cohend024f982012-05-23 18:26:45 -07002183 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002184 if (a != null) {
2185 a.cancel();
2186 }
Adam Cohen19f37922012-03-21 11:59:11 -07002187 }
Adam Cohene7587d22012-05-24 18:50:02 -07002188
Adam Cohen091440a2015-03-18 14:16:05 -07002189 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002190 if (a != null) {
2191 a.cancel();
2192 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002193
Michael Jurka2ecf9952012-06-18 12:52:28 -07002194 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002195 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002196 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002197 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2198 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002199 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2200 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002201 );
2202 s.setDuration(REORDER_ANIMATION_DURATION);
2203 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2204 s.start();
2205 }
Adam Cohen19f37922012-03-21 11:59:11 -07002206 }
2207
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002208 private void completeAndClearReorderPreviewAnimations() {
2209 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002210 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002211 }
2212 mShakeAnimators.clear();
2213 }
2214
Adam Cohen482ed822012-03-02 14:15:13 -08002215 private void commitTempPlacement() {
2216 for (int i = 0; i < mCountX; i++) {
2217 for (int j = 0; j < mCountY; j++) {
2218 mOccupied[i][j] = mTmpOccupied[i][j];
2219 }
2220 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002221 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002222 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002223 View child = mShortcutsAndWidgets.getChildAt(i);
2224 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2225 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002226 // We do a null check here because the item info can be null in the case of the
2227 // AllApps button in the hotseat.
2228 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002229 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2230 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2231 info.requiresDbUpdate = true;
2232 }
Adam Cohen2acce882012-03-28 19:03:19 -07002233 info.cellX = lp.cellX = lp.tmpCellX;
2234 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002235 info.spanX = lp.cellHSpan;
2236 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002237 }
Adam Cohen482ed822012-03-02 14:15:13 -08002238 }
Adam Cohen2acce882012-03-28 19:03:19 -07002239 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002240 }
2241
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002242 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002243 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002244 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002245 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002246 lp.useTmpCoords = useTempCoords;
2247 }
2248 }
2249
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002250 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002251 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2252 int[] result = new int[2];
2253 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002254 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002255 resultSpan);
2256 if (result[0] >= 0 && result[1] >= 0) {
2257 copyCurrentStateToSolution(solution, false);
2258 solution.dragViewX = result[0];
2259 solution.dragViewY = result[1];
2260 solution.dragViewSpanX = resultSpan[0];
2261 solution.dragViewSpanY = resultSpan[1];
2262 solution.isSolution = true;
2263 } else {
2264 solution.isSolution = false;
2265 }
2266 return solution;
2267 }
2268
2269 public void prepareChildForDrag(View child) {
2270 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002271 }
2272
Adam Cohen19f37922012-03-21 11:59:11 -07002273 /* This seems like it should be obvious and straight-forward, but when the direction vector
2274 needs to match with the notion of the dragView pushing other views, we have to employ
2275 a slightly more subtle notion of the direction vector. The question is what two points is
2276 the vector between? The center of the dragView and its desired destination? Not quite, as
2277 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2278 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2279 or right, which helps make pushing feel right.
2280 */
2281 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2282 int spanY, View dragView, int[] resultDirection) {
2283 int[] targetDestination = new int[2];
2284
2285 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2286 Rect dragRect = new Rect();
2287 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2288 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2289
2290 Rect dropRegionRect = new Rect();
2291 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2292 dragView, dropRegionRect, mIntersectingViews);
2293
2294 int dropRegionSpanX = dropRegionRect.width();
2295 int dropRegionSpanY = dropRegionRect.height();
2296
2297 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2298 dropRegionRect.height(), dropRegionRect);
2299
2300 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2301 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2302
2303 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2304 deltaX = 0;
2305 }
2306 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2307 deltaY = 0;
2308 }
2309
2310 if (deltaX == 0 && deltaY == 0) {
2311 // No idea what to do, give a random direction.
2312 resultDirection[0] = 1;
2313 resultDirection[1] = 0;
2314 } else {
2315 computeDirectionVector(deltaX, deltaY, resultDirection);
2316 }
2317 }
2318
2319 // For a given cell and span, fetch the set of views intersecting the region.
2320 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2321 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2322 if (boundingRect != null) {
2323 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2324 }
2325 intersectingViews.clear();
2326 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2327 Rect r1 = new Rect();
2328 final int count = mShortcutsAndWidgets.getChildCount();
2329 for (int i = 0; i < count; i++) {
2330 View child = mShortcutsAndWidgets.getChildAt(i);
2331 if (child == dragView) continue;
2332 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2333 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2334 if (Rect.intersects(r0, r1)) {
2335 mIntersectingViews.add(child);
2336 if (boundingRect != null) {
2337 boundingRect.union(r1);
2338 }
2339 }
2340 }
2341 }
2342
2343 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2344 View dragView, int[] result) {
2345 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2346 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2347 mIntersectingViews);
2348 return !mIntersectingViews.isEmpty();
2349 }
2350
2351 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002352 completeAndClearReorderPreviewAnimations();
2353 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2354 final int count = mShortcutsAndWidgets.getChildCount();
2355 for (int i = 0; i < count; i++) {
2356 View child = mShortcutsAndWidgets.getChildAt(i);
2357 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2358 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2359 lp.tmpCellX = lp.cellX;
2360 lp.tmpCellY = lp.cellY;
2361 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2362 0, false, false);
2363 }
Adam Cohen19f37922012-03-21 11:59:11 -07002364 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002365 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002366 }
Adam Cohen19f37922012-03-21 11:59:11 -07002367 }
2368
Adam Cohenbebf0422012-04-11 18:06:28 -07002369 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2370 View dragView, int[] direction, boolean commit) {
2371 int[] pixelXY = new int[2];
2372 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2373
2374 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002375 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002376 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2377
2378 setUseTempCoords(true);
2379 if (swapSolution != null && swapSolution.isSolution) {
2380 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2381 // committing anything or animating anything as we just want to determine if a solution
2382 // exists
2383 copySolutionToTempState(swapSolution, dragView);
2384 setItemPlacementDirty(true);
2385 animateItemsToSolution(swapSolution, dragView, commit);
2386
2387 if (commit) {
2388 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002389 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002390 setItemPlacementDirty(false);
2391 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002392 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2393 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002394 }
2395 mShortcutsAndWidgets.requestLayout();
2396 }
2397 return swapSolution.isSolution;
2398 }
2399
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002400 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002401 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002402 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002403 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002404
2405 if (resultSpan == null) {
2406 resultSpan = new int[2];
2407 }
2408
Adam Cohen19f37922012-03-21 11:59:11 -07002409 // When we are checking drop validity or actually dropping, we don't recompute the
2410 // direction vector, since we want the solution to match the preview, and it's possible
2411 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002412 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2413 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002414 mDirectionVector[0] = mPreviousReorderDirection[0];
2415 mDirectionVector[1] = mPreviousReorderDirection[1];
2416 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002417 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2418 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2419 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002420 }
2421 } else {
2422 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2423 mPreviousReorderDirection[0] = mDirectionVector[0];
2424 mPreviousReorderDirection[1] = mDirectionVector[1];
2425 }
2426
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002427 // Find a solution involving pushing / displacing any items in the way
2428 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002429 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2430
2431 // We attempt the approach which doesn't shuffle views at all
2432 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2433 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2434
2435 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002436
2437 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2438 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002439 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2440 finalSolution = swapSolution;
2441 } else if (noShuffleSolution.isSolution) {
2442 finalSolution = noShuffleSolution;
2443 }
2444
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002445 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002446 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002447 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2448 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002449 result[0] = finalSolution.dragViewX;
2450 result[1] = finalSolution.dragViewY;
2451 resultSpan[0] = finalSolution.dragViewSpanX;
2452 resultSpan[1] = finalSolution.dragViewSpanY;
2453 } else {
2454 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2455 }
2456 return result;
2457 }
2458
Adam Cohen482ed822012-03-02 14:15:13 -08002459 boolean foundSolution = true;
2460 if (!DESTRUCTIVE_REORDER) {
2461 setUseTempCoords(true);
2462 }
2463
2464 if (finalSolution != null) {
2465 result[0] = finalSolution.dragViewX;
2466 result[1] = finalSolution.dragViewY;
2467 resultSpan[0] = finalSolution.dragViewSpanX;
2468 resultSpan[1] = finalSolution.dragViewSpanY;
2469
2470 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2471 // committing anything or animating anything as we just want to determine if a solution
2472 // exists
2473 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2474 if (!DESTRUCTIVE_REORDER) {
2475 copySolutionToTempState(finalSolution, dragView);
2476 }
2477 setItemPlacementDirty(true);
2478 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2479
Adam Cohen19f37922012-03-21 11:59:11 -07002480 if (!DESTRUCTIVE_REORDER &&
2481 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002482 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002483 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002484 setItemPlacementDirty(false);
2485 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002486 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2487 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002488 }
2489 }
2490 } else {
2491 foundSolution = false;
2492 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2493 }
2494
2495 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2496 setUseTempCoords(false);
2497 }
Adam Cohen482ed822012-03-02 14:15:13 -08002498
Michael Jurkaa52570f2012-03-20 03:18:20 -07002499 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002500 return result;
2501 }
2502
Adam Cohen19f37922012-03-21 11:59:11 -07002503 void setItemPlacementDirty(boolean dirty) {
2504 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002505 }
Adam Cohen19f37922012-03-21 11:59:11 -07002506 boolean isItemPlacementDirty() {
2507 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002508 }
2509
Adam Cohen091440a2015-03-18 14:16:05 -07002510 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002511 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002512 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2513 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002514 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002515 boolean isSolution = false;
2516 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2517
Adam Cohenf3900c22012-11-16 18:28:11 -08002518 void save() {
2519 // Copy current state into savedMap
2520 for (View v: map.keySet()) {
2521 map.get(v).copy(savedMap.get(v));
2522 }
2523 }
2524
2525 void restore() {
2526 // Restore current state from savedMap
2527 for (View v: savedMap.keySet()) {
2528 savedMap.get(v).copy(map.get(v));
2529 }
2530 }
2531
2532 void add(View v, CellAndSpan cs) {
2533 map.put(v, cs);
2534 savedMap.put(v, new CellAndSpan());
2535 sortedViews.add(v);
2536 }
2537
Adam Cohen482ed822012-03-02 14:15:13 -08002538 int area() {
2539 return dragViewSpanX * dragViewSpanY;
2540 }
Adam Cohen8baab352012-03-20 17:39:21 -07002541 }
2542
2543 private class CellAndSpan {
2544 int x, y;
2545 int spanX, spanY;
2546
Adam Cohenf3900c22012-11-16 18:28:11 -08002547 public CellAndSpan() {
2548 }
2549
2550 public void copy(CellAndSpan copy) {
2551 copy.x = x;
2552 copy.y = y;
2553 copy.spanX = spanX;
2554 copy.spanY = spanY;
2555 }
2556
Adam Cohen8baab352012-03-20 17:39:21 -07002557 public CellAndSpan(int x, int y, int spanX, int spanY) {
2558 this.x = x;
2559 this.y = y;
2560 this.spanX = spanX;
2561 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002562 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002563
2564 public String toString() {
2565 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2566 }
2567
Adam Cohen482ed822012-03-02 14:15:13 -08002568 }
2569
Adam Cohendf035382011-04-11 17:22:04 -07002570 /**
Adam Cohendf035382011-04-11 17:22:04 -07002571 * Find a starting cell position that will fit the given bounds nearest the requested
2572 * cell location. Uses Euclidean distance to score multiple vacant areas.
2573 *
2574 * @param pixelX The X location at which you want to search for a vacant area.
2575 * @param pixelY The Y location at which you want to search for a vacant area.
2576 * @param spanX Horizontal span of the object.
2577 * @param spanY Vertical span of the object.
2578 * @param ignoreView Considers space occupied by this view as unoccupied
2579 * @param result Previously returned value to possibly recycle.
2580 * @return The X, Y cell of a vacant area that can contain this object,
2581 * nearest the requested location.
2582 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002583 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2584 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002585 }
2586
Michael Jurka0280c3b2010-09-17 15:00:07 -07002587 boolean existsEmptyCell() {
2588 return findCellForSpan(null, 1, 1);
2589 }
2590
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002591 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002592 * Finds the upper-left coordinate of the first rectangle in the grid that can
2593 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2594 * then this method will only return coordinates for rectangles that contain the cell
2595 * (intersectX, intersectY)
2596 *
2597 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2598 * can be found.
2599 * @param spanX The horizontal span of the cell we want to find.
2600 * @param spanY The vertical span of the cell we want to find.
2601 *
2602 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002603 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002604 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002605 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002606 final int endX = mCountX - (spanX - 1);
2607 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002608
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002609 for (int y = 0; y < endY && !foundCell; y++) {
2610 inner:
2611 for (int x = 0; x < endX; x++) {
2612 for (int i = 0; i < spanX; i++) {
2613 for (int j = 0; j < spanY; j++) {
2614 if (mOccupied[x + i][y + j]) {
2615 // small optimization: we can skip to after the column we just found
2616 // an occupied cell
2617 x += i;
2618 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002619 }
2620 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002621 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002622 if (cellXY != null) {
2623 cellXY[0] = x;
2624 cellXY[1] = y;
2625 }
2626 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002627 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002628 }
2629 }
2630
Michael Jurka28750fb2010-09-24 17:43:49 -07002631 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002632 }
2633
2634 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002635 * A drag event has begun over this layout.
2636 * It may have begun over this layout (in which case onDragChild is called first),
2637 * or it may have begun on another layout.
2638 */
2639 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002640 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002641 mDragging = true;
2642 }
2643
2644 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002645 * Called when drag has left this CellLayout or has been completed (successfully or not)
2646 */
2647 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002648 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002649 // This can actually be called when we aren't in a drag, e.g. when adding a new
2650 // item to this layout via the customize drawer.
2651 // Guard against that case.
2652 if (mDragging) {
2653 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002654 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002655
2656 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002657 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002658 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2659 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002660 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002661 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002662 }
2663
2664 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002665 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002666 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002667 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002668 *
2669 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002670 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002671 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002672 if (child != null) {
2673 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002674 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002675 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002676 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002677 }
2678
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002679 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002680 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002681 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002682 * @param cellX X coordinate of upper left corner expressed as a cell position
2683 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002684 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002685 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002686 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002687 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002688 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002689 final int cellWidth = mCellWidth;
2690 final int cellHeight = mCellHeight;
2691 final int widthGap = mWidthGap;
2692 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002693
Winson Chung4b825dcd2011-06-19 12:41:22 -07002694 final int hStartPadding = getPaddingLeft();
2695 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002696
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002697 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2698 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2699
2700 int x = hStartPadding + cellX * (cellWidth + widthGap);
2701 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002702
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002703 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002704 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002705
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002706 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002707 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002709 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002710 * @param width Width in pixels
2711 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002712 * @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 -08002713 */
Adam Cohen2e6da152015-05-06 11:42:25 -07002714 public static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
Adam Cohen2e6da152015-05-06 11:42:25 -07002715 DeviceProfile grid = launcher.getDeviceProfile();
Sunny Goyalc6205602015-05-21 20:46:33 -07002716 Rect padding = grid.getWorkspacePadding(Utilities.isRtl(launcher.getResources()));
Winson Chung5f8afe62013-08-12 16:19:28 -07002717
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002718 // Always assume we're working with the smallest span to make sure we
2719 // reserve enough space in both orientations.
Sunny Goyalc6205602015-05-21 20:46:33 -07002720 int parentWidth = DeviceProfile.calculateCellWidth(grid.widthPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002721 - padding.left - padding.right, (int) grid.inv.numColumns);
Sunny Goyalc6205602015-05-21 20:46:33 -07002722 int parentHeight = DeviceProfile.calculateCellHeight(grid.heightPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002723 - padding.top - padding.bottom, (int) grid.inv.numRows);
Winson Chung66700732013-08-20 16:56:15 -07002724 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002725
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002726 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002727 int spanX = (int) Math.ceil(width / (float) smallerSize);
2728 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002729
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002730 if (result == null) {
2731 return new int[] { spanX, spanY };
2732 }
2733 result[0] = spanX;
2734 result[1] = spanY;
2735 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002736 }
2737
2738 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002739 * Calculate the grid spans needed to fit given item
2740 */
2741 public void calculateSpans(ItemInfo info) {
2742 final int minWidth;
2743 final int minHeight;
2744
2745 if (info instanceof LauncherAppWidgetInfo) {
2746 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2747 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2748 } else if (info instanceof PendingAddWidgetInfo) {
2749 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2750 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2751 } else {
2752 // It's not a widget, so it must be 1x1
2753 info.spanX = info.spanY = 1;
2754 return;
2755 }
Adam Cohen2e6da152015-05-06 11:42:25 -07002756 int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
Patrick Dubroy047379a2010-12-19 22:02:04 -08002757 info.spanX = spans[0];
2758 info.spanY = spans[1];
2759 }
2760
Michael Jurka0280c3b2010-09-17 15:00:07 -07002761 private void clearOccupiedCells() {
2762 for (int x = 0; x < mCountX; x++) {
2763 for (int y = 0; y < mCountY; y++) {
2764 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002765 }
2766 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002767 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002768
Adam Cohend4844c32011-02-18 19:25:06 -08002769 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002770 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002771 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002772 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002773 }
2774
Adam Cohend4844c32011-02-18 19:25:06 -08002775 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002776 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002777 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002778 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002779 }
2780
Adam Cohen482ed822012-03-02 14:15:13 -08002781 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2782 boolean value) {
2783 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002784 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2785 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002786 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002787 }
2788 }
2789 }
2790
Adam Cohen2801caf2011-05-13 20:57:39 -07002791 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002792 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002793 (Math.max((mCountX - 1), 0) * mWidthGap);
2794 }
2795
2796 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002797 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002798 (Math.max((mCountY - 1), 0) * mHeightGap);
2799 }
2800
Michael Jurka66d72172011-04-12 16:29:25 -07002801 public boolean isOccupied(int x, int y) {
2802 if (x < mCountX && y < mCountY) {
2803 return mOccupied[x][y];
2804 } else {
2805 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2806 }
2807 }
2808
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002809 @Override
2810 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2811 return new CellLayout.LayoutParams(getContext(), attrs);
2812 }
2813
2814 @Override
2815 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2816 return p instanceof CellLayout.LayoutParams;
2817 }
2818
2819 @Override
2820 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2821 return new CellLayout.LayoutParams(p);
2822 }
2823
2824 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2825 /**
2826 * Horizontal location of the item in the grid.
2827 */
2828 @ViewDebug.ExportedProperty
2829 public int cellX;
2830
2831 /**
2832 * Vertical location of the item in the grid.
2833 */
2834 @ViewDebug.ExportedProperty
2835 public int cellY;
2836
2837 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002838 * Temporary horizontal location of the item in the grid during reorder
2839 */
2840 public int tmpCellX;
2841
2842 /**
2843 * Temporary vertical location of the item in the grid during reorder
2844 */
2845 public int tmpCellY;
2846
2847 /**
2848 * Indicates that the temporary coordinates should be used to layout the items
2849 */
2850 public boolean useTmpCoords;
2851
2852 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002853 * Number of cells spanned horizontally by the item.
2854 */
2855 @ViewDebug.ExportedProperty
2856 public int cellHSpan;
2857
2858 /**
2859 * Number of cells spanned vertically by the item.
2860 */
2861 @ViewDebug.ExportedProperty
2862 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002863
Adam Cohen1b607ed2011-03-03 17:26:50 -08002864 /**
2865 * Indicates whether the item will set its x, y, width and height parameters freely,
2866 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2867 */
Adam Cohend4844c32011-02-18 19:25:06 -08002868 public boolean isLockedToGrid = true;
2869
Adam Cohen482ed822012-03-02 14:15:13 -08002870 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002871 * Indicates that this item should use the full extents of its parent.
2872 */
2873 public boolean isFullscreen = false;
2874
2875 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002876 * Indicates whether this item can be reordered. Always true except in the case of the
2877 * the AllApps button.
2878 */
2879 public boolean canReorder = true;
2880
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002881 // X coordinate of the view in the layout.
2882 @ViewDebug.ExportedProperty
2883 int x;
2884 // Y coordinate of the view in the layout.
2885 @ViewDebug.ExportedProperty
2886 int y;
2887
Romain Guy84f296c2009-11-04 15:00:44 -08002888 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002889
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002890 public LayoutParams(Context c, AttributeSet attrs) {
2891 super(c, attrs);
2892 cellHSpan = 1;
2893 cellVSpan = 1;
2894 }
2895
2896 public LayoutParams(ViewGroup.LayoutParams source) {
2897 super(source);
2898 cellHSpan = 1;
2899 cellVSpan = 1;
2900 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002901
2902 public LayoutParams(LayoutParams source) {
2903 super(source);
2904 this.cellX = source.cellX;
2905 this.cellY = source.cellY;
2906 this.cellHSpan = source.cellHSpan;
2907 this.cellVSpan = source.cellVSpan;
2908 }
2909
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002910 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002911 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002912 this.cellX = cellX;
2913 this.cellY = cellY;
2914 this.cellHSpan = cellHSpan;
2915 this.cellVSpan = cellVSpan;
2916 }
2917
Adam Cohen2374abf2013-04-16 14:56:57 -07002918 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2919 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002920 if (isLockedToGrid) {
2921 final int myCellHSpan = cellHSpan;
2922 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002923 int myCellX = useTmpCoords ? tmpCellX : cellX;
2924 int myCellY = useTmpCoords ? tmpCellY : cellY;
2925
2926 if (invertHorizontally) {
2927 myCellX = colCount - myCellX - cellHSpan;
2928 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002929
Adam Cohend4844c32011-02-18 19:25:06 -08002930 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2931 leftMargin - rightMargin;
2932 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2933 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002934 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2935 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002936 }
2937 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002938
Winson Chungaafa03c2010-06-11 17:34:16 -07002939 public String toString() {
2940 return "(" + this.cellX + ", " + this.cellY + ")";
2941 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002942
2943 public void setWidth(int width) {
2944 this.width = width;
2945 }
2946
2947 public int getWidth() {
2948 return width;
2949 }
2950
2951 public void setHeight(int height) {
2952 this.height = height;
2953 }
2954
2955 public int getHeight() {
2956 return height;
2957 }
2958
2959 public void setX(int x) {
2960 this.x = x;
2961 }
2962
2963 public int getX() {
2964 return x;
2965 }
2966
2967 public void setY(int y) {
2968 this.y = y;
2969 }
2970
2971 public int getY() {
2972 return y;
2973 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002974 }
2975
Michael Jurka0280c3b2010-09-17 15:00:07 -07002976 // This class stores info for two purposes:
2977 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2978 // its spanX, spanY, and the screen it is on
2979 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2980 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2981 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002982 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002983 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002984 int cellX = -1;
2985 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002986 int spanX;
2987 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002988 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002989 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002990
Sunny Goyal83a8f042015-05-19 12:52:12 -07002991 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002992 cell = v;
2993 cellX = info.cellX;
2994 cellY = info.cellY;
2995 spanX = info.spanX;
2996 spanY = info.spanY;
2997 screenId = info.screenId;
2998 container = info.container;
2999 }
3000
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003001 @Override
3002 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003003 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3004 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003005 }
3006 }
Michael Jurkad771c962011-08-09 15:00:48 -07003007
Sunny Goyala9116722015-04-29 13:55:58 -07003008 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
3009 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
3010 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003011
3012 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3013 int x2 = x + spanX - 1;
3014 int y2 = y + spanY - 1;
3015 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3016 return false;
3017 }
3018 for (int i = x; i <= x2; i++) {
3019 for (int j = y; j <= y2; j++) {
3020 if (mOccupied[i][j]) {
3021 return false;
3022 }
3023 }
3024 }
3025
3026 return true;
3027 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003028}