blob: 4bf862d0dcab0441cc76ddd0b4817f41fff87547 [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) {
Adam Cohen2e6da152015-05-06 11:42:25 -0700824 DeviceProfile grid = mLauncher.getDeviceProfile();
Winson Chung5f8afe62013-08-12 16:19:28 -0700825
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800826 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800827 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700828 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
829 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700830 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
831 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700832 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700833 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
834 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700835 if (cw != mCellWidth || ch != mCellHeight) {
836 mCellWidth = cw;
837 mCellHeight = ch;
838 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
839 mHeightGap, mCountX, mCountY);
840 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700841 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700842
Winson Chung2d75f122013-09-23 16:53:31 -0700843 int newWidth = childWidthSize;
844 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700845 if (mFixedWidth > 0 && mFixedHeight > 0) {
846 newWidth = mFixedWidth;
847 newHeight = mFixedHeight;
848 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800849 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
850 }
851
Adam Cohend22015c2010-07-26 22:02:18 -0700852 int numWidthGaps = mCountX - 1;
853 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800854
Adam Cohen234c4cd2011-07-17 21:03:04 -0700855 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700856 int hSpace = childWidthSize;
857 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700858 int hFreeSpace = hSpace - (mCountX * mCellWidth);
859 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700860 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
861 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700862 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
863 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700864 } else {
865 mWidthGap = mOriginalWidthGap;
866 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700867 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700868
869 // Make the feedback view large enough to hold the blur bitmap.
870 mTouchFeedbackView.measure(
871 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
872 MeasureSpec.EXACTLY),
873 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
874 MeasureSpec.EXACTLY));
875
876 mShortcutsAndWidgets.measure(
877 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
878 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
879
880 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
881 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700882 if (mFixedWidth > 0 && mFixedHeight > 0) {
883 setMeasuredDimension(maxWidth, maxHeight);
884 } else {
885 setMeasuredDimension(widthSize, heightSize);
886 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800887 }
888
889 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700890 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700891 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
892 (mCountX * mCellWidth);
893 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
894 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700895
896 mTouchFeedbackView.layout(left, top,
897 left + mTouchFeedbackView.getMeasuredWidth(),
898 top + mTouchFeedbackView.getMeasuredHeight());
899 mShortcutsAndWidgets.layout(left, top,
900 left + r - l,
901 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800902 }
903
904 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700905 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
906 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700907
908 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700909 mBackground.getPadding(mTempRect);
910 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
911 w + mTempRect.right, h + mTempRect.bottom);
Winson Chung82a9bd22013-10-08 16:02:34 -0700912
Sunny Goyal2805e632015-05-20 15:35:32 -0700913 mOverScrollLeft.setBounds(0, 0, w, h);
914 mOverScrollRight.setBounds(0, 0, w, h);
Michael Jurkadee05892010-07-27 10:01:56 -0700915 }
916
917 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800918 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700919 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800920 }
921
922 @Override
923 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700924 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800925 }
926
Michael Jurka5f1c5092010-09-03 14:15:02 -0700927 public float getBackgroundAlpha() {
928 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700929 }
930
Michael Jurka5f1c5092010-09-03 14:15:02 -0700931 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800932 if (mBackgroundAlpha != alpha) {
933 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700934 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800935 }
Michael Jurkadee05892010-07-27 10:01:56 -0700936 }
937
Sunny Goyal2805e632015-05-20 15:35:32 -0700938 @Override
939 protected boolean verifyDrawable(Drawable who) {
940 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
941 }
942
Michael Jurkaa52570f2012-03-20 03:18:20 -0700943 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700944 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700945 }
946
Michael Jurkaa52570f2012-03-20 03:18:20 -0700947 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700948 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700949 }
950
Patrick Dubroy440c3602010-07-13 17:50:32 -0700951 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700952 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700953 }
954
Adam Cohen76fc0852011-06-17 13:26:23 -0700955 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800956 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700957 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800958 boolean[][] occupied = mOccupied;
959 if (!permanent) {
960 occupied = mTmpOccupied;
961 }
962
Adam Cohen19f37922012-03-21 11:59:11 -0700963 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700964 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
965 final ItemInfo info = (ItemInfo) child.getTag();
966
967 // We cancel any existing animations
968 if (mReorderAnimators.containsKey(lp)) {
969 mReorderAnimators.get(lp).cancel();
970 mReorderAnimators.remove(lp);
971 }
972
Adam Cohen482ed822012-03-02 14:15:13 -0800973 final int oldX = lp.x;
974 final int oldY = lp.y;
975 if (adjustOccupied) {
976 occupied[lp.cellX][lp.cellY] = false;
977 occupied[cellX][cellY] = true;
978 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700979 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800980 if (permanent) {
981 lp.cellX = info.cellX = cellX;
982 lp.cellY = info.cellY = cellY;
983 } else {
984 lp.tmpCellX = cellX;
985 lp.tmpCellY = cellY;
986 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700987 clc.setupLp(lp);
988 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800989 final int newX = lp.x;
990 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700991
Adam Cohen76fc0852011-06-17 13:26:23 -0700992 lp.x = oldX;
993 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700994
Adam Cohen482ed822012-03-02 14:15:13 -0800995 // Exit early if we're not actually moving the view
996 if (oldX == newX && oldY == newY) {
997 lp.isLockedToGrid = true;
998 return true;
999 }
1000
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001001 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001002 va.setDuration(duration);
1003 mReorderAnimators.put(lp, va);
1004
1005 va.addUpdateListener(new AnimatorUpdateListener() {
1006 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001007 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001008 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001009 lp.x = (int) ((1 - r) * oldX + r * newX);
1010 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001011 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001012 }
1013 });
Adam Cohen482ed822012-03-02 14:15:13 -08001014 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001015 boolean cancelled = false;
1016 public void onAnimationEnd(Animator animation) {
1017 // If the animation was cancelled, it means that another animation
1018 // has interrupted this one, and we don't want to lock the item into
1019 // place just yet.
1020 if (!cancelled) {
1021 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001022 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001023 }
1024 if (mReorderAnimators.containsKey(lp)) {
1025 mReorderAnimators.remove(lp);
1026 }
1027 }
1028 public void onAnimationCancel(Animator animation) {
1029 cancelled = true;
1030 }
1031 });
Adam Cohen482ed822012-03-02 14:15:13 -08001032 va.setStartDelay(delay);
1033 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001034 return true;
1035 }
1036 return false;
1037 }
1038
Adam Cohen482ed822012-03-02 14:15:13 -08001039 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1040 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001041 final int oldDragCellX = mDragCell[0];
1042 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001043
Adam Cohen2801caf2011-05-13 20:57:39 -07001044 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001045 return;
1046 }
1047
Adam Cohen482ed822012-03-02 14:15:13 -08001048 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1049 mDragCell[0] = cellX;
1050 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001051 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001052 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001053 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001054
Joe Onorato4be866d2010-10-10 11:26:02 -07001055 int left = topLeft[0];
1056 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001057
Winson Chungb8c69f32011-10-19 21:36:08 -07001058 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001059 // When drawing the drag outline, it did not account for margin offsets
1060 // added by the view's parent.
1061 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1062 left += lp.leftMargin;
1063 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001064
Adam Cohen99e8b402011-03-25 19:23:43 -07001065 // Offsets due to the size difference between the View and the dragOutline.
1066 // There is a size difference to account for the outer blur, which may lie
1067 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001068 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001069 // We center about the x axis
1070 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1071 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001072 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001073 if (dragOffset != null && dragRegion != null) {
1074 // Center the drag region *horizontally* in the cell and apply a drag
1075 // outline offset
1076 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1077 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001078 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1079 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1080 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001081 } else {
1082 // Center the drag outline in the cell
1083 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1084 - dragOutline.getWidth()) / 2;
1085 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1086 - dragOutline.getHeight()) / 2;
1087 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001088 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001089 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001090 mDragOutlineAnims[oldIndex].animateOut();
1091 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001092 Rect r = mDragOutlines[mDragOutlineCurrent];
1093 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1094 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001095 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001096 }
Winson Chung150fbab2010-09-29 17:14:26 -07001097
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001098 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1099 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001100 }
1101 }
1102
Adam Cohene0310962011-04-18 16:15:31 -07001103 public void clearDragOutlines() {
1104 final int oldIndex = mDragOutlineCurrent;
1105 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001106 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001107 }
1108
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001109 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001110 * Find a vacant area that will fit the given bounds nearest the requested
1111 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001112 *
Romain Guy51afc022009-05-04 18:03:43 -07001113 * @param pixelX The X location at which you want to search for a vacant area.
1114 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001115 * @param spanX Horizontal span of the object.
1116 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001117 * @param result Array in which to place the result, or null (in which case a new array will
1118 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001119 * @return The X, Y cell of a vacant area that can contain this object,
1120 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001121 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001122 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1123 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001124 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001125
Michael Jurka6a1435d2010-09-27 17:35:12 -07001126 /**
1127 * Find a vacant area that will fit the given bounds nearest the requested
1128 * cell location. Uses Euclidean distance to score multiple vacant areas.
1129 *
1130 * @param pixelX The X location at which you want to search for a vacant area.
1131 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001132 * @param minSpanX The minimum horizontal span required
1133 * @param minSpanY The minimum vertical span required
1134 * @param spanX Horizontal span of the object.
1135 * @param spanY Vertical span of the object.
1136 * @param result Array in which to place the result, or null (in which case a new array will
1137 * be allocated)
1138 * @return The X, Y cell of a vacant area that can contain this object,
1139 * nearest the requested location.
1140 */
1141 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1142 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001143 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001144 result, resultSpan);
1145 }
1146
Adam Cohend41fbf52012-02-16 23:53:59 -08001147 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1148 private void lazyInitTempRectStack() {
1149 if (mTempRectStack.isEmpty()) {
1150 for (int i = 0; i < mCountX * mCountY; i++) {
1151 mTempRectStack.push(new Rect());
1152 }
1153 }
1154 }
Adam Cohen482ed822012-03-02 14:15:13 -08001155
Adam Cohend41fbf52012-02-16 23:53:59 -08001156 private void recycleTempRects(Stack<Rect> used) {
1157 while (!used.isEmpty()) {
1158 mTempRectStack.push(used.pop());
1159 }
1160 }
1161
1162 /**
1163 * Find a vacant area that will fit the given bounds nearest the requested
1164 * cell location. Uses Euclidean distance to score multiple vacant areas.
1165 *
1166 * @param pixelX The X location at which you want to search for a vacant area.
1167 * @param pixelY The Y location at which you want to search for a vacant area.
1168 * @param minSpanX The minimum horizontal span required
1169 * @param minSpanY The minimum vertical span required
1170 * @param spanX Horizontal span of the object.
1171 * @param spanY Vertical span of the object.
1172 * @param ignoreOccupied If true, the result can be an occupied cell
1173 * @param result Array in which to place the result, or null (in which case a new array will
1174 * be allocated)
1175 * @return The X, Y cell of a vacant area that can contain this object,
1176 * nearest the requested location.
1177 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001178 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1179 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001180 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001181
Adam Cohene3e27a82011-04-15 12:07:39 -07001182 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1183 // to the center of the item, but we are searching based on the top-left cell, so
1184 // we translate the point over to correspond to the top-left.
1185 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1186 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1187
Jeff Sharkey70864282009-04-07 21:08:40 -07001188 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001189 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001190 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001191 final Rect bestRect = new Rect(-1, -1, -1, -1);
1192 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001193
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001194 final int countX = mCountX;
1195 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001196
Adam Cohend41fbf52012-02-16 23:53:59 -08001197 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1198 spanX < minSpanX || spanY < minSpanY) {
1199 return bestXY;
1200 }
1201
1202 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001203 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001204 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1205 int ySize = -1;
1206 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001207 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001208 // First, let's see if this thing fits anywhere
1209 for (int i = 0; i < minSpanX; i++) {
1210 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001211 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001212 continue inner;
1213 }
Michael Jurkac28de512010-08-13 11:27:44 -07001214 }
1215 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001216 xSize = minSpanX;
1217 ySize = minSpanY;
1218
1219 // We know that the item will fit at _some_ acceptable size, now let's see
1220 // how big we can make it. We'll alternate between incrementing x and y spans
1221 // until we hit a limit.
1222 boolean incX = true;
1223 boolean hitMaxX = xSize >= spanX;
1224 boolean hitMaxY = ySize >= spanY;
1225 while (!(hitMaxX && hitMaxY)) {
1226 if (incX && !hitMaxX) {
1227 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001228 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001229 // We can't move out horizontally
1230 hitMaxX = true;
1231 }
1232 }
1233 if (!hitMaxX) {
1234 xSize++;
1235 }
1236 } else if (!hitMaxY) {
1237 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001238 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001239 // We can't move out vertically
1240 hitMaxY = true;
1241 }
1242 }
1243 if (!hitMaxY) {
1244 ySize++;
1245 }
1246 }
1247 hitMaxX |= xSize >= spanX;
1248 hitMaxY |= ySize >= spanY;
1249 incX = !incX;
1250 }
1251 incX = true;
1252 hitMaxX = xSize >= spanX;
1253 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001254 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001255 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001256 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001257
Adam Cohend41fbf52012-02-16 23:53:59 -08001258 // We verify that the current rect is not a sub-rect of any of our previous
1259 // candidates. In this case, the current rect is disqualified in favour of the
1260 // containing rect.
1261 Rect currentRect = mTempRectStack.pop();
1262 currentRect.set(x, y, x + xSize, y + ySize);
1263 boolean contained = false;
1264 for (Rect r : validRegions) {
1265 if (r.contains(currentRect)) {
1266 contained = true;
1267 break;
1268 }
1269 }
1270 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001271 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001272
Adam Cohend41fbf52012-02-16 23:53:59 -08001273 if ((distance <= bestDistance && !contained) ||
1274 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001275 bestDistance = distance;
1276 bestXY[0] = x;
1277 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001278 if (resultSpan != null) {
1279 resultSpan[0] = xSize;
1280 resultSpan[1] = ySize;
1281 }
1282 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001283 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001284 }
1285 }
1286
Adam Cohenc0dcf592011-06-01 15:30:43 -07001287 // Return -1, -1 if no suitable location found
1288 if (bestDistance == Double.MAX_VALUE) {
1289 bestXY[0] = -1;
1290 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001291 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001292 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001293 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001294 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001295
Adam Cohen482ed822012-03-02 14:15:13 -08001296 /**
1297 * Find a vacant area that will fit the given bounds nearest the requested
1298 * cell location, and will also weigh in a suggested direction vector of the
1299 * desired location. This method computers distance based on unit grid distances,
1300 * not pixel distances.
1301 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001302 * @param cellX The X cell nearest to which you want to search for a vacant area.
1303 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001304 * @param spanX Horizontal span of the object.
1305 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001306 * @param direction The favored direction in which the views should move from x, y
1307 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1308 * matches exactly. Otherwise we find the best matching direction.
1309 * @param occoupied The array which represents which cells in the CellLayout are occupied
1310 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001311 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001312 * @param result Array in which to place the result, or null (in which case a new array will
1313 * be allocated)
1314 * @return The X, Y cell of a vacant area that can contain this object,
1315 * nearest the requested location.
1316 */
1317 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001318 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001319 // Keep track of best-scoring drop area
1320 final int[] bestXY = result != null ? result : new int[2];
1321 float bestDistance = Float.MAX_VALUE;
1322 int bestDirectionScore = Integer.MIN_VALUE;
1323
1324 final int countX = mCountX;
1325 final int countY = mCountY;
1326
1327 for (int y = 0; y < countY - (spanY - 1); y++) {
1328 inner:
1329 for (int x = 0; x < countX - (spanX - 1); x++) {
1330 // First, let's see if this thing fits anywhere
1331 for (int i = 0; i < spanX; i++) {
1332 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001333 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001334 continue inner;
1335 }
1336 }
1337 }
1338
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001339 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001340 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001341 computeDirectionVector(x - cellX, y - cellY, curDirection);
1342 // The direction score is just the dot product of the two candidate direction
1343 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001344 int curDirectionScore = direction[0] * curDirection[0] +
1345 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001346 boolean exactDirectionOnly = false;
1347 boolean directionMatches = direction[0] == curDirection[0] &&
1348 direction[0] == curDirection[0];
1349 if ((directionMatches || !exactDirectionOnly) &&
1350 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001351 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1352 bestDistance = distance;
1353 bestDirectionScore = curDirectionScore;
1354 bestXY[0] = x;
1355 bestXY[1] = y;
1356 }
1357 }
1358 }
1359
1360 // Return -1, -1 if no suitable location found
1361 if (bestDistance == Float.MAX_VALUE) {
1362 bestXY[0] = -1;
1363 bestXY[1] = -1;
1364 }
1365 return bestXY;
1366 }
1367
1368 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001369 int[] direction, ItemConfiguration currentState) {
1370 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001371 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001372 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001373 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1374
Adam Cohen8baab352012-03-20 17:39:21 -07001375 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001376
1377 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001378 c.x = mTempLocation[0];
1379 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001380 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001381 }
Adam Cohen8baab352012-03-20 17:39:21 -07001382 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001383 return success;
1384 }
1385
Adam Cohenf3900c22012-11-16 18:28:11 -08001386 /**
1387 * This helper class defines a cluster of views. It helps with defining complex edges
1388 * of the cluster and determining how those edges interact with other views. The edges
1389 * essentially define a fine-grained boundary around the cluster of views -- like a more
1390 * precise version of a bounding box.
1391 */
1392 private class ViewCluster {
1393 final static int LEFT = 0;
1394 final static int TOP = 1;
1395 final static int RIGHT = 2;
1396 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001397
Adam Cohenf3900c22012-11-16 18:28:11 -08001398 ArrayList<View> views;
1399 ItemConfiguration config;
1400 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001401
Adam Cohenf3900c22012-11-16 18:28:11 -08001402 int[] leftEdge = new int[mCountY];
1403 int[] rightEdge = new int[mCountY];
1404 int[] topEdge = new int[mCountX];
1405 int[] bottomEdge = new int[mCountX];
1406 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1407
1408 @SuppressWarnings("unchecked")
1409 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1410 this.views = (ArrayList<View>) views.clone();
1411 this.config = config;
1412 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001413 }
1414
Adam Cohenf3900c22012-11-16 18:28:11 -08001415 void resetEdges() {
1416 for (int i = 0; i < mCountX; i++) {
1417 topEdge[i] = -1;
1418 bottomEdge[i] = -1;
1419 }
1420 for (int i = 0; i < mCountY; i++) {
1421 leftEdge[i] = -1;
1422 rightEdge[i] = -1;
1423 }
1424 leftEdgeDirty = true;
1425 rightEdgeDirty = true;
1426 bottomEdgeDirty = true;
1427 topEdgeDirty = true;
1428 boundingRectDirty = true;
1429 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001430
Adam Cohenf3900c22012-11-16 18:28:11 -08001431 void computeEdge(int which, int[] edge) {
1432 int count = views.size();
1433 for (int i = 0; i < count; i++) {
1434 CellAndSpan cs = config.map.get(views.get(i));
1435 switch (which) {
1436 case LEFT:
1437 int left = cs.x;
1438 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1439 if (left < edge[j] || edge[j] < 0) {
1440 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001441 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001442 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001443 break;
1444 case RIGHT:
1445 int right = cs.x + cs.spanX;
1446 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1447 if (right > edge[j]) {
1448 edge[j] = right;
1449 }
1450 }
1451 break;
1452 case TOP:
1453 int top = cs.y;
1454 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1455 if (top < edge[j] || edge[j] < 0) {
1456 edge[j] = top;
1457 }
1458 }
1459 break;
1460 case BOTTOM:
1461 int bottom = cs.y + cs.spanY;
1462 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1463 if (bottom > edge[j]) {
1464 edge[j] = bottom;
1465 }
1466 }
1467 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001468 }
1469 }
1470 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001471
1472 boolean isViewTouchingEdge(View v, int whichEdge) {
1473 CellAndSpan cs = config.map.get(v);
1474
1475 int[] edge = getEdge(whichEdge);
1476
1477 switch (whichEdge) {
1478 case LEFT:
1479 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1480 if (edge[i] == cs.x + cs.spanX) {
1481 return true;
1482 }
1483 }
1484 break;
1485 case RIGHT:
1486 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1487 if (edge[i] == cs.x) {
1488 return true;
1489 }
1490 }
1491 break;
1492 case TOP:
1493 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1494 if (edge[i] == cs.y + cs.spanY) {
1495 return true;
1496 }
1497 }
1498 break;
1499 case BOTTOM:
1500 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1501 if (edge[i] == cs.y) {
1502 return true;
1503 }
1504 }
1505 break;
1506 }
1507 return false;
1508 }
1509
1510 void shift(int whichEdge, int delta) {
1511 for (View v: views) {
1512 CellAndSpan c = config.map.get(v);
1513 switch (whichEdge) {
1514 case LEFT:
1515 c.x -= delta;
1516 break;
1517 case RIGHT:
1518 c.x += delta;
1519 break;
1520 case TOP:
1521 c.y -= delta;
1522 break;
1523 case BOTTOM:
1524 default:
1525 c.y += delta;
1526 break;
1527 }
1528 }
1529 resetEdges();
1530 }
1531
1532 public void addView(View v) {
1533 views.add(v);
1534 resetEdges();
1535 }
1536
1537 public Rect getBoundingRect() {
1538 if (boundingRectDirty) {
1539 boolean first = true;
1540 for (View v: views) {
1541 CellAndSpan c = config.map.get(v);
1542 if (first) {
1543 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1544 first = false;
1545 } else {
1546 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1547 }
1548 }
1549 }
1550 return boundingRect;
1551 }
1552
1553 public int[] getEdge(int which) {
1554 switch (which) {
1555 case LEFT:
1556 return getLeftEdge();
1557 case RIGHT:
1558 return getRightEdge();
1559 case TOP:
1560 return getTopEdge();
1561 case BOTTOM:
1562 default:
1563 return getBottomEdge();
1564 }
1565 }
1566
1567 public int[] getLeftEdge() {
1568 if (leftEdgeDirty) {
1569 computeEdge(LEFT, leftEdge);
1570 }
1571 return leftEdge;
1572 }
1573
1574 public int[] getRightEdge() {
1575 if (rightEdgeDirty) {
1576 computeEdge(RIGHT, rightEdge);
1577 }
1578 return rightEdge;
1579 }
1580
1581 public int[] getTopEdge() {
1582 if (topEdgeDirty) {
1583 computeEdge(TOP, topEdge);
1584 }
1585 return topEdge;
1586 }
1587
1588 public int[] getBottomEdge() {
1589 if (bottomEdgeDirty) {
1590 computeEdge(BOTTOM, bottomEdge);
1591 }
1592 return bottomEdge;
1593 }
1594
1595 PositionComparator comparator = new PositionComparator();
1596 class PositionComparator implements Comparator<View> {
1597 int whichEdge = 0;
1598 public int compare(View left, View right) {
1599 CellAndSpan l = config.map.get(left);
1600 CellAndSpan r = config.map.get(right);
1601 switch (whichEdge) {
1602 case LEFT:
1603 return (r.x + r.spanX) - (l.x + l.spanX);
1604 case RIGHT:
1605 return l.x - r.x;
1606 case TOP:
1607 return (r.y + r.spanY) - (l.y + l.spanY);
1608 case BOTTOM:
1609 default:
1610 return l.y - r.y;
1611 }
1612 }
1613 }
1614
1615 public void sortConfigurationForEdgePush(int edge) {
1616 comparator.whichEdge = edge;
1617 Collections.sort(config.sortedViews, comparator);
1618 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001619 }
1620
Adam Cohenf3900c22012-11-16 18:28:11 -08001621 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1622 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001623
Adam Cohenf3900c22012-11-16 18:28:11 -08001624 ViewCluster cluster = new ViewCluster(views, currentState);
1625 Rect clusterRect = cluster.getBoundingRect();
1626 int whichEdge;
1627 int pushDistance;
1628 boolean fail = false;
1629
1630 // Determine the edge of the cluster that will be leading the push and how far
1631 // the cluster must be shifted.
1632 if (direction[0] < 0) {
1633 whichEdge = ViewCluster.LEFT;
1634 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001635 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001636 whichEdge = ViewCluster.RIGHT;
1637 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1638 } else if (direction[1] < 0) {
1639 whichEdge = ViewCluster.TOP;
1640 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1641 } else {
1642 whichEdge = ViewCluster.BOTTOM;
1643 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001644 }
1645
Adam Cohenf3900c22012-11-16 18:28:11 -08001646 // Break early for invalid push distance.
1647 if (pushDistance <= 0) {
1648 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001649 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001650
1651 // Mark the occupied state as false for the group of views we want to move.
1652 for (View v: views) {
1653 CellAndSpan c = currentState.map.get(v);
1654 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1655 }
1656
1657 // We save the current configuration -- if we fail to find a solution we will revert
1658 // to the initial state. The process of finding a solution modifies the configuration
1659 // in place, hence the need for revert in the failure case.
1660 currentState.save();
1661
1662 // The pushing algorithm is simplified by considering the views in the order in which
1663 // they would be pushed by the cluster. For example, if the cluster is leading with its
1664 // left edge, we consider sort the views by their right edge, from right to left.
1665 cluster.sortConfigurationForEdgePush(whichEdge);
1666
1667 while (pushDistance > 0 && !fail) {
1668 for (View v: currentState.sortedViews) {
1669 // For each view that isn't in the cluster, we see if the leading edge of the
1670 // cluster is contacting the edge of that view. If so, we add that view to the
1671 // cluster.
1672 if (!cluster.views.contains(v) && v != dragView) {
1673 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1674 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1675 if (!lp.canReorder) {
1676 // The push solution includes the all apps button, this is not viable.
1677 fail = true;
1678 break;
1679 }
1680 cluster.addView(v);
1681 CellAndSpan c = currentState.map.get(v);
1682
1683 // Adding view to cluster, mark it as not occupied.
1684 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1685 }
1686 }
1687 }
1688 pushDistance--;
1689
1690 // The cluster has been completed, now we move the whole thing over in the appropriate
1691 // direction.
1692 cluster.shift(whichEdge, 1);
1693 }
1694
1695 boolean foundSolution = false;
1696 clusterRect = cluster.getBoundingRect();
1697
1698 // Due to the nature of the algorithm, the only check required to verify a valid solution
1699 // is to ensure that completed shifted cluster lies completely within the cell layout.
1700 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1701 clusterRect.bottom <= mCountY) {
1702 foundSolution = true;
1703 } else {
1704 currentState.restore();
1705 }
1706
1707 // In either case, we set the occupied array as marked for the location of the views
1708 for (View v: cluster.views) {
1709 CellAndSpan c = currentState.map.get(v);
1710 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1711 }
1712
1713 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001714 }
1715
Adam Cohen482ed822012-03-02 14:15:13 -08001716 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001717 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001718 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001719
Adam Cohen8baab352012-03-20 17:39:21 -07001720 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001721 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001722 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001723 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001724 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001725 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001726 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001727 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001728 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001729 }
1730 }
Adam Cohen8baab352012-03-20 17:39:21 -07001731
Adam Cohen8baab352012-03-20 17:39:21 -07001732 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001733 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001734 CellAndSpan c = currentState.map.get(v);
1735 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1736 }
1737
Adam Cohen47a876d2012-03-19 13:21:41 -07001738 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1739 int top = boundingRect.top;
1740 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001741 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001742 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001743 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001744 CellAndSpan c = currentState.map.get(v);
1745 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001746 }
1747
Adam Cohen482ed822012-03-02 14:15:13 -08001748 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1749
Adam Cohenf3900c22012-11-16 18:28:11 -08001750 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1751 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001752
Adam Cohen8baab352012-03-20 17:39:21 -07001753 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001754 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001755 int deltaX = mTempLocation[0] - boundingRect.left;
1756 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001757 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001758 CellAndSpan c = currentState.map.get(v);
1759 c.x += deltaX;
1760 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001761 }
1762 success = true;
1763 }
Adam Cohen8baab352012-03-20 17:39:21 -07001764
1765 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001766 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001767 CellAndSpan c = currentState.map.get(v);
1768 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001769 }
1770 return success;
1771 }
1772
1773 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1774 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1775 }
1776
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001777 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1778 // to push items in each of the cardinal directions, in an order based on the direction vector
1779 // passed.
1780 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1781 int[] direction, View ignoreView, ItemConfiguration solution) {
1782 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001783 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001784 // separately in each of the components.
1785 int temp = direction[1];
1786 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001787
1788 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001789 ignoreView, solution)) {
1790 return true;
1791 }
1792 direction[1] = temp;
1793 temp = direction[0];
1794 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001795
1796 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001797 ignoreView, solution)) {
1798 return true;
1799 }
1800 // Revert the direction
1801 direction[0] = temp;
1802
1803 // Now we try pushing in each component of the opposite direction
1804 direction[0] *= -1;
1805 direction[1] *= -1;
1806 temp = direction[1];
1807 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001808 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001809 ignoreView, solution)) {
1810 return true;
1811 }
1812
1813 direction[1] = temp;
1814 temp = direction[0];
1815 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001816 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001817 ignoreView, solution)) {
1818 return true;
1819 }
1820 // revert the direction
1821 direction[0] = temp;
1822 direction[0] *= -1;
1823 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001824
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001825 } else {
1826 // If the direction vector has a single non-zero component, we push first in the
1827 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001828 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001829 ignoreView, solution)) {
1830 return true;
1831 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001832 // Then we try the opposite direction
1833 direction[0] *= -1;
1834 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001835 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001836 ignoreView, solution)) {
1837 return true;
1838 }
1839 // Switch the direction back
1840 direction[0] *= -1;
1841 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001842
1843 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001844 // to find a solution by pushing along the perpendicular axis.
1845
1846 // Swap the components
1847 int temp = direction[1];
1848 direction[1] = direction[0];
1849 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001850 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001851 ignoreView, solution)) {
1852 return true;
1853 }
1854
1855 // Then we try the opposite direction
1856 direction[0] *= -1;
1857 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001858 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001859 ignoreView, solution)) {
1860 return true;
1861 }
1862 // Switch the direction back
1863 direction[0] *= -1;
1864 direction[1] *= -1;
1865
1866 // Swap the components back
1867 temp = direction[1];
1868 direction[1] = direction[0];
1869 direction[0] = temp;
1870 }
1871 return false;
1872 }
1873
Adam Cohen482ed822012-03-02 14:15:13 -08001874 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001875 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001876 // Return early if get invalid cell positions
1877 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001878
Adam Cohen8baab352012-03-20 17:39:21 -07001879 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001880 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001881
Adam Cohen8baab352012-03-20 17:39:21 -07001882 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001883 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001884 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001885 if (c != null) {
1886 c.x = cellX;
1887 c.y = cellY;
1888 }
Adam Cohen482ed822012-03-02 14:15:13 -08001889 }
Adam Cohen482ed822012-03-02 14:15:13 -08001890 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1891 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001892 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001893 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001894 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001895 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001896 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001897 if (Rect.intersects(r0, r1)) {
1898 if (!lp.canReorder) {
1899 return false;
1900 }
1901 mIntersectingViews.add(child);
1902 }
1903 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001904
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001905 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1906
Winson Chung5f8afe62013-08-12 16:19:28 -07001907 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001908 // we try to find a solution such that no displaced item travels through another item
1909 // without also displacing that item.
1910 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001911 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001912 return true;
1913 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001914
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001915 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001916 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001917 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001918 return true;
1919 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001920
Adam Cohen482ed822012-03-02 14:15:13 -08001921 // Ok, they couldn't move as a block, let's move them individually
1922 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001923 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001924 return false;
1925 }
1926 }
1927 return true;
1928 }
1929
1930 /*
1931 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1932 * the provided point and the provided cell
1933 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001934 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001935 double angle = Math.atan(((float) deltaY) / deltaX);
1936
1937 result[0] = 0;
1938 result[1] = 0;
1939 if (Math.abs(Math.cos(angle)) > 0.5f) {
1940 result[0] = (int) Math.signum(deltaX);
1941 }
1942 if (Math.abs(Math.sin(angle)) > 0.5f) {
1943 result[1] = (int) Math.signum(deltaY);
1944 }
1945 }
1946
Adam Cohen8baab352012-03-20 17:39:21 -07001947 private void copyOccupiedArray(boolean[][] occupied) {
1948 for (int i = 0; i < mCountX; i++) {
1949 for (int j = 0; j < mCountY; j++) {
1950 occupied[i][j] = mOccupied[i][j];
1951 }
1952 }
1953 }
1954
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001955 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001956 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1957 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001958 // Copy the current state into the solution. This solution will be manipulated as necessary.
1959 copyCurrentStateToSolution(solution, false);
1960 // Copy the current occupied array into the temporary occupied array. This array will be
1961 // manipulated as necessary to find a solution.
1962 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001963
1964 // We find the nearest cell into which we would place the dragged item, assuming there's
1965 // nothing in its way.
1966 int result[] = new int[2];
1967 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1968
1969 boolean success = false;
1970 // First we try the exact nearest position of the item being dragged,
1971 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001972 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1973 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001974
1975 if (!success) {
1976 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1977 // x, then 1 in y etc.
1978 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001979 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1980 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001981 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001982 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1983 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001984 }
1985 solution.isSolution = false;
1986 } else {
1987 solution.isSolution = true;
1988 solution.dragViewX = result[0];
1989 solution.dragViewY = result[1];
1990 solution.dragViewSpanX = spanX;
1991 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001992 }
1993 return solution;
1994 }
1995
1996 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001997 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001998 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001999 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002000 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002001 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002002 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002003 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002004 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002005 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002006 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002007 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002008 }
2009 }
2010
2011 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2012 for (int i = 0; i < mCountX; i++) {
2013 for (int j = 0; j < mCountY; j++) {
2014 mTmpOccupied[i][j] = false;
2015 }
2016 }
2017
Michael Jurkaa52570f2012-03-20 03:18:20 -07002018 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002019 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002020 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002021 if (child == dragView) continue;
2022 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002023 CellAndSpan c = solution.map.get(child);
2024 if (c != null) {
2025 lp.tmpCellX = c.x;
2026 lp.tmpCellY = c.y;
2027 lp.cellHSpan = c.spanX;
2028 lp.cellVSpan = c.spanY;
2029 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002030 }
2031 }
2032 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2033 solution.dragViewSpanY, mTmpOccupied, true);
2034 }
2035
2036 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2037 commitDragView) {
2038
2039 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2040 for (int i = 0; i < mCountX; i++) {
2041 for (int j = 0; j < mCountY; j++) {
2042 occupied[i][j] = false;
2043 }
2044 }
2045
Michael Jurkaa52570f2012-03-20 03:18:20 -07002046 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002047 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002048 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002049 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002050 CellAndSpan c = solution.map.get(child);
2051 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002052 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2053 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002054 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002055 }
2056 }
2057 if (commitDragView) {
2058 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2059 solution.dragViewSpanY, occupied, true);
2060 }
2061 }
2062
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002063
2064 // This method starts or changes the reorder preview animations
2065 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2066 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002067 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002068 for (int i = 0; i < childCount; i++) {
2069 View child = mShortcutsAndWidgets.getChildAt(i);
2070 if (child == dragView) continue;
2071 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002072 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2073 != null && !solution.intersectingViews.contains(child);
2074
Adam Cohen19f37922012-03-21 11:59:11 -07002075 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002076 if (c != null && !skip) {
2077 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2078 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002079 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002080 }
2081 }
2082 }
2083
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002084 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002085 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002086 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002087 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002088 float finalDeltaX;
2089 float finalDeltaY;
2090 float initDeltaX;
2091 float initDeltaY;
2092 float finalScale;
2093 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002094 int mode;
2095 boolean repeating = false;
2096 private static final int PREVIEW_DURATION = 300;
2097 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2098
2099 public static final int MODE_HINT = 0;
2100 public static final int MODE_PREVIEW = 1;
2101
Adam Cohene7587d22012-05-24 18:50:02 -07002102 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002103
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002104 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2105 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002106 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2107 final int x0 = mTmpPoint[0];
2108 final int y0 = mTmpPoint[1];
2109 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2110 final int x1 = mTmpPoint[0];
2111 final int y1 = mTmpPoint[1];
2112 final int dX = x1 - x0;
2113 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002114 finalDeltaX = 0;
2115 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002116 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002117 if (dX == dY && dX == 0) {
2118 } else {
2119 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002120 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002121 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002122 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002123 } else {
2124 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002125 finalDeltaX = (int) (- dir * Math.signum(dX) *
2126 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2127 finalDeltaY = (int) (- dir * Math.signum(dY) *
2128 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002129 }
2130 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002131 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002132 initDeltaX = child.getTranslationX();
2133 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002134 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002135 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002136 this.child = child;
2137 }
2138
Adam Cohend024f982012-05-23 18:26:45 -07002139 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002140 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002141 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002142 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002143 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002144 if (finalDeltaX == 0 && finalDeltaY == 0) {
2145 completeAnimationImmediately();
2146 return;
2147 }
Adam Cohen19f37922012-03-21 11:59:11 -07002148 }
Adam Cohend024f982012-05-23 18:26:45 -07002149 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002150 return;
2151 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002152 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002153 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002154 va.setRepeatMode(ValueAnimator.REVERSE);
2155 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002156 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002157 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002158 va.addUpdateListener(new AnimatorUpdateListener() {
2159 @Override
2160 public void onAnimationUpdate(ValueAnimator animation) {
2161 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002162 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2163 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2164 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002165 child.setTranslationX(x);
2166 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002167 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002168 child.setScaleX(s);
2169 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002170 }
2171 });
2172 va.addListener(new AnimatorListenerAdapter() {
2173 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002174 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002175 initDeltaX = 0;
2176 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002177 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002178 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002179 }
2180 });
Adam Cohen19f37922012-03-21 11:59:11 -07002181 mShakeAnimators.put(child, this);
2182 va.start();
2183 }
2184
Adam Cohend024f982012-05-23 18:26:45 -07002185 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002186 if (a != null) {
2187 a.cancel();
2188 }
Adam Cohen19f37922012-03-21 11:59:11 -07002189 }
Adam Cohene7587d22012-05-24 18:50:02 -07002190
Adam Cohen091440a2015-03-18 14:16:05 -07002191 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002192 if (a != null) {
2193 a.cancel();
2194 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002195
Michael Jurka2ecf9952012-06-18 12:52:28 -07002196 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002197 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002198 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002199 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2200 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002201 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2202 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002203 );
2204 s.setDuration(REORDER_ANIMATION_DURATION);
2205 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2206 s.start();
2207 }
Adam Cohen19f37922012-03-21 11:59:11 -07002208 }
2209
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002210 private void completeAndClearReorderPreviewAnimations() {
2211 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002212 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002213 }
2214 mShakeAnimators.clear();
2215 }
2216
Adam Cohen482ed822012-03-02 14:15:13 -08002217 private void commitTempPlacement() {
2218 for (int i = 0; i < mCountX; i++) {
2219 for (int j = 0; j < mCountY; j++) {
2220 mOccupied[i][j] = mTmpOccupied[i][j];
2221 }
2222 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002223 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002224 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002225 View child = mShortcutsAndWidgets.getChildAt(i);
2226 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2227 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002228 // We do a null check here because the item info can be null in the case of the
2229 // AllApps button in the hotseat.
2230 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002231 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2232 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2233 info.requiresDbUpdate = true;
2234 }
Adam Cohen2acce882012-03-28 19:03:19 -07002235 info.cellX = lp.cellX = lp.tmpCellX;
2236 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002237 info.spanX = lp.cellHSpan;
2238 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002239 }
Adam Cohen482ed822012-03-02 14:15:13 -08002240 }
Adam Cohen2acce882012-03-28 19:03:19 -07002241 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002242 }
2243
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002244 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002245 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002246 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002247 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002248 lp.useTmpCoords = useTempCoords;
2249 }
2250 }
2251
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002252 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002253 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2254 int[] result = new int[2];
2255 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002256 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002257 resultSpan);
2258 if (result[0] >= 0 && result[1] >= 0) {
2259 copyCurrentStateToSolution(solution, false);
2260 solution.dragViewX = result[0];
2261 solution.dragViewY = result[1];
2262 solution.dragViewSpanX = resultSpan[0];
2263 solution.dragViewSpanY = resultSpan[1];
2264 solution.isSolution = true;
2265 } else {
2266 solution.isSolution = false;
2267 }
2268 return solution;
2269 }
2270
2271 public void prepareChildForDrag(View child) {
2272 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002273 }
2274
Adam Cohen19f37922012-03-21 11:59:11 -07002275 /* This seems like it should be obvious and straight-forward, but when the direction vector
2276 needs to match with the notion of the dragView pushing other views, we have to employ
2277 a slightly more subtle notion of the direction vector. The question is what two points is
2278 the vector between? The center of the dragView and its desired destination? Not quite, as
2279 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2280 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2281 or right, which helps make pushing feel right.
2282 */
2283 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2284 int spanY, View dragView, int[] resultDirection) {
2285 int[] targetDestination = new int[2];
2286
2287 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2288 Rect dragRect = new Rect();
2289 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2290 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2291
2292 Rect dropRegionRect = new Rect();
2293 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2294 dragView, dropRegionRect, mIntersectingViews);
2295
2296 int dropRegionSpanX = dropRegionRect.width();
2297 int dropRegionSpanY = dropRegionRect.height();
2298
2299 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2300 dropRegionRect.height(), dropRegionRect);
2301
2302 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2303 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2304
2305 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2306 deltaX = 0;
2307 }
2308 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2309 deltaY = 0;
2310 }
2311
2312 if (deltaX == 0 && deltaY == 0) {
2313 // No idea what to do, give a random direction.
2314 resultDirection[0] = 1;
2315 resultDirection[1] = 0;
2316 } else {
2317 computeDirectionVector(deltaX, deltaY, resultDirection);
2318 }
2319 }
2320
2321 // For a given cell and span, fetch the set of views intersecting the region.
2322 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2323 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2324 if (boundingRect != null) {
2325 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2326 }
2327 intersectingViews.clear();
2328 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2329 Rect r1 = new Rect();
2330 final int count = mShortcutsAndWidgets.getChildCount();
2331 for (int i = 0; i < count; i++) {
2332 View child = mShortcutsAndWidgets.getChildAt(i);
2333 if (child == dragView) continue;
2334 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2335 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2336 if (Rect.intersects(r0, r1)) {
2337 mIntersectingViews.add(child);
2338 if (boundingRect != null) {
2339 boundingRect.union(r1);
2340 }
2341 }
2342 }
2343 }
2344
2345 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2346 View dragView, int[] result) {
2347 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2348 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2349 mIntersectingViews);
2350 return !mIntersectingViews.isEmpty();
2351 }
2352
2353 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002354 completeAndClearReorderPreviewAnimations();
2355 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2356 final int count = mShortcutsAndWidgets.getChildCount();
2357 for (int i = 0; i < count; i++) {
2358 View child = mShortcutsAndWidgets.getChildAt(i);
2359 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2360 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2361 lp.tmpCellX = lp.cellX;
2362 lp.tmpCellY = lp.cellY;
2363 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2364 0, false, false);
2365 }
Adam Cohen19f37922012-03-21 11:59:11 -07002366 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002367 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002368 }
Adam Cohen19f37922012-03-21 11:59:11 -07002369 }
2370
Adam Cohenbebf0422012-04-11 18:06:28 -07002371 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2372 View dragView, int[] direction, boolean commit) {
2373 int[] pixelXY = new int[2];
2374 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2375
2376 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002377 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002378 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2379
2380 setUseTempCoords(true);
2381 if (swapSolution != null && swapSolution.isSolution) {
2382 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2383 // committing anything or animating anything as we just want to determine if a solution
2384 // exists
2385 copySolutionToTempState(swapSolution, dragView);
2386 setItemPlacementDirty(true);
2387 animateItemsToSolution(swapSolution, dragView, commit);
2388
2389 if (commit) {
2390 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002391 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002392 setItemPlacementDirty(false);
2393 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002394 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2395 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002396 }
2397 mShortcutsAndWidgets.requestLayout();
2398 }
2399 return swapSolution.isSolution;
2400 }
2401
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002402 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002403 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002404 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002405 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002406
2407 if (resultSpan == null) {
2408 resultSpan = new int[2];
2409 }
2410
Adam Cohen19f37922012-03-21 11:59:11 -07002411 // When we are checking drop validity or actually dropping, we don't recompute the
2412 // direction vector, since we want the solution to match the preview, and it's possible
2413 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002414 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2415 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002416 mDirectionVector[0] = mPreviousReorderDirection[0];
2417 mDirectionVector[1] = mPreviousReorderDirection[1];
2418 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002419 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2420 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2421 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002422 }
2423 } else {
2424 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2425 mPreviousReorderDirection[0] = mDirectionVector[0];
2426 mPreviousReorderDirection[1] = mDirectionVector[1];
2427 }
2428
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002429 // Find a solution involving pushing / displacing any items in the way
2430 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002431 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2432
2433 // We attempt the approach which doesn't shuffle views at all
2434 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2435 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2436
2437 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002438
2439 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2440 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002441 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2442 finalSolution = swapSolution;
2443 } else if (noShuffleSolution.isSolution) {
2444 finalSolution = noShuffleSolution;
2445 }
2446
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002447 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002448 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002449 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2450 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002451 result[0] = finalSolution.dragViewX;
2452 result[1] = finalSolution.dragViewY;
2453 resultSpan[0] = finalSolution.dragViewSpanX;
2454 resultSpan[1] = finalSolution.dragViewSpanY;
2455 } else {
2456 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2457 }
2458 return result;
2459 }
2460
Adam Cohen482ed822012-03-02 14:15:13 -08002461 boolean foundSolution = true;
2462 if (!DESTRUCTIVE_REORDER) {
2463 setUseTempCoords(true);
2464 }
2465
2466 if (finalSolution != null) {
2467 result[0] = finalSolution.dragViewX;
2468 result[1] = finalSolution.dragViewY;
2469 resultSpan[0] = finalSolution.dragViewSpanX;
2470 resultSpan[1] = finalSolution.dragViewSpanY;
2471
2472 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2473 // committing anything or animating anything as we just want to determine if a solution
2474 // exists
2475 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2476 if (!DESTRUCTIVE_REORDER) {
2477 copySolutionToTempState(finalSolution, dragView);
2478 }
2479 setItemPlacementDirty(true);
2480 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2481
Adam Cohen19f37922012-03-21 11:59:11 -07002482 if (!DESTRUCTIVE_REORDER &&
2483 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002484 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002485 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002486 setItemPlacementDirty(false);
2487 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002488 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2489 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002490 }
2491 }
2492 } else {
2493 foundSolution = false;
2494 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2495 }
2496
2497 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2498 setUseTempCoords(false);
2499 }
Adam Cohen482ed822012-03-02 14:15:13 -08002500
Michael Jurkaa52570f2012-03-20 03:18:20 -07002501 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002502 return result;
2503 }
2504
Adam Cohen19f37922012-03-21 11:59:11 -07002505 void setItemPlacementDirty(boolean dirty) {
2506 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002507 }
Adam Cohen19f37922012-03-21 11:59:11 -07002508 boolean isItemPlacementDirty() {
2509 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002510 }
2511
Adam Cohen091440a2015-03-18 14:16:05 -07002512 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002513 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002514 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2515 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002516 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002517 boolean isSolution = false;
2518 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2519
Adam Cohenf3900c22012-11-16 18:28:11 -08002520 void save() {
2521 // Copy current state into savedMap
2522 for (View v: map.keySet()) {
2523 map.get(v).copy(savedMap.get(v));
2524 }
2525 }
2526
2527 void restore() {
2528 // Restore current state from savedMap
2529 for (View v: savedMap.keySet()) {
2530 savedMap.get(v).copy(map.get(v));
2531 }
2532 }
2533
2534 void add(View v, CellAndSpan cs) {
2535 map.put(v, cs);
2536 savedMap.put(v, new CellAndSpan());
2537 sortedViews.add(v);
2538 }
2539
Adam Cohen482ed822012-03-02 14:15:13 -08002540 int area() {
2541 return dragViewSpanX * dragViewSpanY;
2542 }
Adam Cohen8baab352012-03-20 17:39:21 -07002543 }
2544
2545 private class CellAndSpan {
2546 int x, y;
2547 int spanX, spanY;
2548
Adam Cohenf3900c22012-11-16 18:28:11 -08002549 public CellAndSpan() {
2550 }
2551
2552 public void copy(CellAndSpan copy) {
2553 copy.x = x;
2554 copy.y = y;
2555 copy.spanX = spanX;
2556 copy.spanY = spanY;
2557 }
2558
Adam Cohen8baab352012-03-20 17:39:21 -07002559 public CellAndSpan(int x, int y, int spanX, int spanY) {
2560 this.x = x;
2561 this.y = y;
2562 this.spanX = spanX;
2563 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002564 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002565
2566 public String toString() {
2567 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2568 }
2569
Adam Cohen482ed822012-03-02 14:15:13 -08002570 }
2571
Adam Cohendf035382011-04-11 17:22:04 -07002572 /**
Adam Cohendf035382011-04-11 17:22:04 -07002573 * Find a starting cell position that will fit the given bounds nearest the requested
2574 * cell location. Uses Euclidean distance to score multiple vacant areas.
2575 *
2576 * @param pixelX The X location at which you want to search for a vacant area.
2577 * @param pixelY The Y location at which you want to search for a vacant area.
2578 * @param spanX Horizontal span of the object.
2579 * @param spanY Vertical span of the object.
2580 * @param ignoreView Considers space occupied by this view as unoccupied
2581 * @param result Previously returned value to possibly recycle.
2582 * @return The X, Y cell of a vacant area that can contain this object,
2583 * nearest the requested location.
2584 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002585 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2586 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002587 }
2588
Michael Jurka0280c3b2010-09-17 15:00:07 -07002589 boolean existsEmptyCell() {
2590 return findCellForSpan(null, 1, 1);
2591 }
2592
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002593 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002594 * Finds the upper-left coordinate of the first rectangle in the grid that can
2595 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2596 * then this method will only return coordinates for rectangles that contain the cell
2597 * (intersectX, intersectY)
2598 *
2599 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2600 * can be found.
2601 * @param spanX The horizontal span of the cell we want to find.
2602 * @param spanY The vertical span of the cell we want to find.
2603 *
2604 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002605 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002606 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002607 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002608 final int endX = mCountX - (spanX - 1);
2609 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002610
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002611 for (int y = 0; y < endY && !foundCell; y++) {
2612 inner:
2613 for (int x = 0; x < endX; x++) {
2614 for (int i = 0; i < spanX; i++) {
2615 for (int j = 0; j < spanY; j++) {
2616 if (mOccupied[x + i][y + j]) {
2617 // small optimization: we can skip to after the column we just found
2618 // an occupied cell
2619 x += i;
2620 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002621 }
2622 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002623 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002624 if (cellXY != null) {
2625 cellXY[0] = x;
2626 cellXY[1] = y;
2627 }
2628 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002629 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002630 }
2631 }
2632
Michael Jurka28750fb2010-09-24 17:43:49 -07002633 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002634 }
2635
2636 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002637 * A drag event has begun over this layout.
2638 * It may have begun over this layout (in which case onDragChild is called first),
2639 * or it may have begun on another layout.
2640 */
2641 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002642 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002643 mDragging = true;
2644 }
2645
2646 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002647 * Called when drag has left this CellLayout or has been completed (successfully or not)
2648 */
2649 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002650 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002651 // This can actually be called when we aren't in a drag, e.g. when adding a new
2652 // item to this layout via the customize drawer.
2653 // Guard against that case.
2654 if (mDragging) {
2655 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002656 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002657
2658 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002659 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002660 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2661 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002662 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002663 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002664 }
2665
2666 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002667 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002668 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002669 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002670 *
2671 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002672 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002673 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002674 if (child != null) {
2675 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002676 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002677 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002678 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002679 }
2680
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002681 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002682 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002683 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002684 * @param cellX X coordinate of upper left corner expressed as a cell position
2685 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002686 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002687 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002688 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002689 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002690 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002691 final int cellWidth = mCellWidth;
2692 final int cellHeight = mCellHeight;
2693 final int widthGap = mWidthGap;
2694 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002695
Winson Chung4b825dcd2011-06-19 12:41:22 -07002696 final int hStartPadding = getPaddingLeft();
2697 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002698
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2700 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2701
2702 int x = hStartPadding + cellX * (cellWidth + widthGap);
2703 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002704
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002705 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002706 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002707
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002709 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002710 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002711 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002712 * @param width Width in pixels
2713 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002714 * @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 -08002715 */
Adam Cohen2e6da152015-05-06 11:42:25 -07002716 public static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002717 LauncherAppState app = LauncherAppState.getInstance();
Adam Cohen2e6da152015-05-06 11:42:25 -07002718 DeviceProfile grid = launcher.getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002719 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2720 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002721
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002722 // Always assume we're working with the smallest span to make sure we
2723 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002724 int parentWidth = grid.calculateCellWidth(grid.widthPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002725 - padding.left - padding.right, (int) grid.inv.numColumns);
Winson Chung66700732013-08-20 16:56:15 -07002726 int parentHeight = grid.calculateCellHeight(grid.heightPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002727 - padding.top - padding.bottom, (int) grid.inv.numRows);
Winson Chung66700732013-08-20 16:56:15 -07002728 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002729
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002730 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002731 int spanX = (int) Math.ceil(width / (float) smallerSize);
2732 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002733
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002734 if (result == null) {
2735 return new int[] { spanX, spanY };
2736 }
2737 result[0] = spanX;
2738 result[1] = spanY;
2739 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002740 }
2741
2742 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002743 * Calculate the grid spans needed to fit given item
2744 */
2745 public void calculateSpans(ItemInfo info) {
2746 final int minWidth;
2747 final int minHeight;
2748
2749 if (info instanceof LauncherAppWidgetInfo) {
2750 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2751 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2752 } else if (info instanceof PendingAddWidgetInfo) {
2753 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2754 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2755 } else {
2756 // It's not a widget, so it must be 1x1
2757 info.spanX = info.spanY = 1;
2758 return;
2759 }
Adam Cohen2e6da152015-05-06 11:42:25 -07002760 int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
Patrick Dubroy047379a2010-12-19 22:02:04 -08002761 info.spanX = spans[0];
2762 info.spanY = spans[1];
2763 }
2764
Michael Jurka0280c3b2010-09-17 15:00:07 -07002765 private void clearOccupiedCells() {
2766 for (int x = 0; x < mCountX; x++) {
2767 for (int y = 0; y < mCountY; y++) {
2768 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002769 }
2770 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002771 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002772
Adam Cohend4844c32011-02-18 19:25:06 -08002773 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002774 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002775 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002776 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002777 }
2778
Adam Cohend4844c32011-02-18 19:25:06 -08002779 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002780 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002781 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002782 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002783 }
2784
Adam Cohen482ed822012-03-02 14:15:13 -08002785 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2786 boolean value) {
2787 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002788 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2789 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002790 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002791 }
2792 }
2793 }
2794
Adam Cohen2801caf2011-05-13 20:57:39 -07002795 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002796 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002797 (Math.max((mCountX - 1), 0) * mWidthGap);
2798 }
2799
2800 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002801 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002802 (Math.max((mCountY - 1), 0) * mHeightGap);
2803 }
2804
Michael Jurka66d72172011-04-12 16:29:25 -07002805 public boolean isOccupied(int x, int y) {
2806 if (x < mCountX && y < mCountY) {
2807 return mOccupied[x][y];
2808 } else {
2809 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2810 }
2811 }
2812
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002813 @Override
2814 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2815 return new CellLayout.LayoutParams(getContext(), attrs);
2816 }
2817
2818 @Override
2819 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2820 return p instanceof CellLayout.LayoutParams;
2821 }
2822
2823 @Override
2824 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2825 return new CellLayout.LayoutParams(p);
2826 }
2827
2828 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2829 /**
2830 * Horizontal location of the item in the grid.
2831 */
2832 @ViewDebug.ExportedProperty
2833 public int cellX;
2834
2835 /**
2836 * Vertical location of the item in the grid.
2837 */
2838 @ViewDebug.ExportedProperty
2839 public int cellY;
2840
2841 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002842 * Temporary horizontal location of the item in the grid during reorder
2843 */
2844 public int tmpCellX;
2845
2846 /**
2847 * Temporary vertical location of the item in the grid during reorder
2848 */
2849 public int tmpCellY;
2850
2851 /**
2852 * Indicates that the temporary coordinates should be used to layout the items
2853 */
2854 public boolean useTmpCoords;
2855
2856 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002857 * Number of cells spanned horizontally by the item.
2858 */
2859 @ViewDebug.ExportedProperty
2860 public int cellHSpan;
2861
2862 /**
2863 * Number of cells spanned vertically by the item.
2864 */
2865 @ViewDebug.ExportedProperty
2866 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002867
Adam Cohen1b607ed2011-03-03 17:26:50 -08002868 /**
2869 * Indicates whether the item will set its x, y, width and height parameters freely,
2870 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2871 */
Adam Cohend4844c32011-02-18 19:25:06 -08002872 public boolean isLockedToGrid = true;
2873
Adam Cohen482ed822012-03-02 14:15:13 -08002874 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002875 * Indicates that this item should use the full extents of its parent.
2876 */
2877 public boolean isFullscreen = false;
2878
2879 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002880 * Indicates whether this item can be reordered. Always true except in the case of the
2881 * the AllApps button.
2882 */
2883 public boolean canReorder = true;
2884
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002885 // X coordinate of the view in the layout.
2886 @ViewDebug.ExportedProperty
2887 int x;
2888 // Y coordinate of the view in the layout.
2889 @ViewDebug.ExportedProperty
2890 int y;
2891
Romain Guy84f296c2009-11-04 15:00:44 -08002892 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002893
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002894 public LayoutParams(Context c, AttributeSet attrs) {
2895 super(c, attrs);
2896 cellHSpan = 1;
2897 cellVSpan = 1;
2898 }
2899
2900 public LayoutParams(ViewGroup.LayoutParams source) {
2901 super(source);
2902 cellHSpan = 1;
2903 cellVSpan = 1;
2904 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002905
2906 public LayoutParams(LayoutParams source) {
2907 super(source);
2908 this.cellX = source.cellX;
2909 this.cellY = source.cellY;
2910 this.cellHSpan = source.cellHSpan;
2911 this.cellVSpan = source.cellVSpan;
2912 }
2913
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002914 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002915 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002916 this.cellX = cellX;
2917 this.cellY = cellY;
2918 this.cellHSpan = cellHSpan;
2919 this.cellVSpan = cellVSpan;
2920 }
2921
Adam Cohen2374abf2013-04-16 14:56:57 -07002922 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2923 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002924 if (isLockedToGrid) {
2925 final int myCellHSpan = cellHSpan;
2926 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002927 int myCellX = useTmpCoords ? tmpCellX : cellX;
2928 int myCellY = useTmpCoords ? tmpCellY : cellY;
2929
2930 if (invertHorizontally) {
2931 myCellX = colCount - myCellX - cellHSpan;
2932 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002933
Adam Cohend4844c32011-02-18 19:25:06 -08002934 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2935 leftMargin - rightMargin;
2936 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2937 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002938 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2939 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002940 }
2941 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002942
Winson Chungaafa03c2010-06-11 17:34:16 -07002943 public String toString() {
2944 return "(" + this.cellX + ", " + this.cellY + ")";
2945 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002946
2947 public void setWidth(int width) {
2948 this.width = width;
2949 }
2950
2951 public int getWidth() {
2952 return width;
2953 }
2954
2955 public void setHeight(int height) {
2956 this.height = height;
2957 }
2958
2959 public int getHeight() {
2960 return height;
2961 }
2962
2963 public void setX(int x) {
2964 this.x = x;
2965 }
2966
2967 public int getX() {
2968 return x;
2969 }
2970
2971 public void setY(int y) {
2972 this.y = y;
2973 }
2974
2975 public int getY() {
2976 return y;
2977 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002978 }
2979
Michael Jurka0280c3b2010-09-17 15:00:07 -07002980 // This class stores info for two purposes:
2981 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2982 // its spanX, spanY, and the screen it is on
2983 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2984 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2985 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002986 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002987 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002988 int cellX = -1;
2989 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002990 int spanX;
2991 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002992 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002993 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002994
Sunny Goyal83a8f042015-05-19 12:52:12 -07002995 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002996 cell = v;
2997 cellX = info.cellX;
2998 cellY = info.cellY;
2999 spanX = info.spanX;
3000 spanY = info.spanY;
3001 screenId = info.screenId;
3002 container = info.container;
3003 }
3004
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003005 @Override
3006 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003007 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3008 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003009 }
3010 }
Michael Jurkad771c962011-08-09 15:00:48 -07003011
Sunny Goyala9116722015-04-29 13:55:58 -07003012 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
3013 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
3014 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003015
3016 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3017 int x2 = x + spanX - 1;
3018 int y2 = y + spanY - 1;
3019 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3020 return false;
3021 }
3022 for (int i = x; i <= x2; i++) {
3023 for (int j = y; j <= y2; j++) {
3024 if (mOccupied[i][j]) {
3025 return false;
3026 }
3027 }
3028 }
3029
3030 return true;
3031 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003032}