blob: b875d22e62bec660657884bd99d2cc14c6283667 [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070020import android.animation.AnimatorListenerAdapter;
Brandon Keely50e6e562012-05-08 16:28:49 -070021import android.animation.AnimatorSet;
Chet Haase00397b12010-10-07 11:13:10 -070022import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070023import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohenc9735cf2015-01-23 16:11:55 -080025import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070029import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070030import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080031import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070032import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070033import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080034import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080035import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070036import android.graphics.drawable.Drawable;
Sunny Goyal2805e632015-05-20 15:35:32 -070037import android.graphics.drawable.TransitionDrawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080038import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080040import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070042import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070043import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080044import android.view.MotionEvent;
45import android.view.View;
46import android.view.ViewDebug;
47import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080048import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070049import android.view.animation.DecelerateInterpolator;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080050
Sunny Goyal4b6eb262015-05-14 19:24:40 -070051import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Daniel Sandler325dc232013-06-05 22:57:57 -040052import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyale9b651e2015-04-24 11:44:51 -070053import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
54import com.android.launcher3.accessibility.FolderAccessibilityHelper;
55import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Adam Cohen091440a2015-03-18 14:16:05 -070056import com.android.launcher3.util.Thunk;
Hyunyoung Song3f471442015-04-08 19:01:34 -070057import com.android.launcher3.widget.PendingAddWidgetInfo;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070058
Adam Cohen69ce2e52011-07-03 19:25:21 -070059import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070060import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080061import java.util.Collections;
62import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070063import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080064import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070065
Sunny Goyal4b6eb262015-05-14 19:24:40 -070066public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070067 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
68 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
69
Winson Chungaafa03c2010-06-11 17:34:16 -070070 static final String TAG = "CellLayout";
71
Adam Cohen2acce882012-03-28 19:03:19 -070072 private Launcher mLauncher;
Adam Cohen091440a2015-03-18 14:16:05 -070073 @Thunk int mCellWidth;
74 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070075 private int mFixedCellWidth;
76 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070077
Adam Cohen091440a2015-03-18 14:16:05 -070078 @Thunk int mCountX;
79 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080080
Adam Cohen234c4cd2011-07-17 21:03:04 -070081 private int mOriginalWidthGap;
82 private int mOriginalHeightGap;
Adam Cohen091440a2015-03-18 14:16:05 -070083 @Thunk int mWidthGap;
84 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070085 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070086 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070087 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088
Patrick Dubroyde7658b2010-09-27 11:15:43 -070089 // These are temporary variables to prevent having to allocate a new object just to
90 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -070091 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -070092 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070093
The Android Open Source Project31dd5032009-03-03 19:32:27 -080094 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080095 boolean[][] mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080096
Michael Jurkadee05892010-07-27 10:01:56 -070097 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -070098 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -070099
Adam Cohen69ce2e52011-07-03 19:25:21 -0700100 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700101 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700102
Michael Jurka5f1c5092010-09-03 14:15:02 -0700103 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700104
Sunny Goyal2805e632015-05-20 15:35:32 -0700105 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
106 private final TransitionDrawable mBackground;
107
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700108 // These values allow a fixed measurement to be set on the CellLayout.
109 private int mFixedWidth = -1;
110 private int mFixedHeight = -1;
111
Michael Jurka33945b22010-12-21 18:19:38 -0800112 // If we're actively dragging something over this screen, mIsDragOverlapping is true
113 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700114
Winson Chung150fbab2010-09-29 17:14:26 -0700115 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700116 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700117 @Thunk Rect[] mDragOutlines = new Rect[4];
118 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700119 private InterruptibleInOutAnimator[] mDragOutlineAnims =
120 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700121
122 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700123 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700124 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700125
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700126 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800127
Sunny Goyal316490e2015-06-02 09:38:28 -0700128 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
129 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700130
131 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700132
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700133 // When a drag operation is in progress, holds the nearest cell to the touch point
134 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800135
Joe Onorato4be866d2010-10-10 11:26:02 -0700136 private boolean mDragging = false;
137
Patrick Dubroyce34a972010-10-19 10:34:32 -0700138 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700139 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700140
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800141 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700142 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800143
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800144 public static final int MODE_SHOW_REORDER_HINT = 0;
145 public static final int MODE_DRAG_OVER = 1;
146 public static final int MODE_ON_DROP = 2;
147 public static final int MODE_ON_DROP_EXTERNAL = 3;
148 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700149 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800150 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
151
Adam Cohena897f392012-04-27 18:12:05 -0700152 static final int LANDSCAPE = 0;
153 static final int PORTRAIT = 1;
154
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800155 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700156 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700157 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700158
Adam Cohen482ed822012-03-02 14:15:13 -0800159 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
160 private Rect mOccupiedRect = new Rect();
161 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700162 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700163 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800164
Sunny Goyal2805e632015-05-20 15:35:32 -0700165 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700166
Michael Jurkaca993832012-06-29 15:17:04 -0700167 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700168
Adam Cohenc9735cf2015-01-23 16:11:55 -0800169 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700170 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800171 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800172
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800173 public CellLayout(Context context) {
174 this(context, null);
175 }
176
177 public CellLayout(Context context, AttributeSet attrs) {
178 this(context, attrs, 0);
179 }
180
181 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
182 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700183
184 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
185 // the user where a dragged item will land when dropped.
186 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800187 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700188 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700189
Adam Cohen2e6da152015-05-06 11:42:25 -0700190 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800191 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
192
Winson Chung11a1a532013-09-13 11:14:45 -0700193 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800194 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700195 mWidthGap = mOriginalWidthGap = 0;
196 mHeightGap = mOriginalHeightGap = 0;
197 mMaxGap = Integer.MAX_VALUE;
Adam Cohen2e6da152015-05-06 11:42:25 -0700198 mCountX = (int) grid.inv.numColumns;
199 mCountY = (int) grid.inv.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700200 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800201 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700202 mPreviousReorderDirection[0] = INVALID_DIRECTION;
203 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800204
205 a.recycle();
206
207 setAlwaysDrawnWithCacheEnabled(false);
208
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700209 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700210 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700211
Sunny Goyal2805e632015-05-20 15:35:32 -0700212 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
213 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700214 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800215
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800216 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700217 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700218
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700219 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700220 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700221 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700222 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800223 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700224 }
225
226 // When dragging things around the home screens, we show a green outline of
227 // where the item will land. The outlines gradually fade out, leaving a trail
228 // behind the drag path.
229 // Set up all the animations that are used to implement this fading.
230 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700231 final float fromAlphaValue = 0;
232 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700233
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700234 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700235
236 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700237 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100238 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700239 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700240 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700241 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700242 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700243 final Bitmap outline = (Bitmap)anim.getTag();
244
245 // If an animation is started and then stopped very quickly, we can still
246 // get spurious updates we've cleared the tag. Guard against this.
247 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700248 @SuppressWarnings("all") // suppress dead code warning
249 final boolean debug = false;
250 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700251 Object val = animation.getAnimatedValue();
252 Log.d(TAG, "anim " + thisIndex + " update: " + val +
253 ", isStopped " + anim.isStopped());
254 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700255 // Try to prevent it from continuing to run
256 animation.cancel();
257 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700258 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800259 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700260 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700261 }
262 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700263 // The animation holds a reference to the drag outline bitmap as long is it's
264 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700265 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700266 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700268 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 anim.setTag(null);
270 }
271 }
272 });
273 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700274 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700275
Michael Jurkaa52570f2012-03-20 03:18:20 -0700276 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700277 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700278 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700279
Mady Melloref044dd2015-06-02 15:35:07 -0700280 mStylusEventHelper = new StylusEventHelper(this);
281
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700282 mTouchFeedbackView = new ClickShadowView(context);
283 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700284 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700285 }
286
Adam Cohenc9735cf2015-01-23 16:11:55 -0800287 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700288 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800289 mUseTouchHelper = enable;
290 if (!enable) {
291 ViewCompat.setAccessibilityDelegate(this, null);
292 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
293 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
294 setOnClickListener(mLauncher);
295 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700296 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
297 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
298 mTouchHelper = new WorkspaceAccessibilityHelper(this);
299 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
300 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
301 mTouchHelper = new FolderAccessibilityHelper(this);
302 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800303 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
304 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
305 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
306 setOnClickListener(mTouchHelper);
307 }
308
309 // Invalidate the accessibility hierarchy
310 if (getParent() != null) {
311 getParent().notifySubtreeAccessibilityStateChanged(
312 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
313 }
314 }
315
316 @Override
317 public boolean dispatchHoverEvent(MotionEvent event) {
318 // Always attempt to dispatch hover events to accessibility first.
319 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
320 return true;
321 }
322 return super.dispatchHoverEvent(event);
323 }
324
325 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800326 public boolean onInterceptTouchEvent(MotionEvent ev) {
327 if (mUseTouchHelper ||
328 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
329 return true;
330 }
331 return false;
332 }
333
Mady Melloref044dd2015-06-02 15:35:07 -0700334 @Override
335 public boolean onTouchEvent(MotionEvent ev) {
336 boolean handled = super.onTouchEvent(ev);
337 // Stylus button press on a home screen should not switch between overview mode and
338 // the home screen mode, however, once in overview mode stylus button press should be
339 // enabled to allow rearranging the different home screens. So check what mode
340 // the workspace is in, and only perform stylus button presses while in overview mode.
341 if (mLauncher.mWorkspace.isInOverviewMode()
342 && mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
343 return true;
344 }
345 return handled;
346 }
347
Chris Craik01f2d7f2013-10-01 14:41:56 -0700348 public void enableHardwareLayer(boolean hasLayer) {
349 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700350 }
351
352 public void buildHardwareLayer() {
353 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700354 }
355
Adam Cohen307fe232012-08-16 17:55:58 -0700356 public float getChildrenScale() {
357 return mIsHotseat ? mHotseatScale : 1.0f;
358 }
359
Winson Chung5f8afe62013-08-12 16:19:28 -0700360 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700361 mFixedCellWidth = mCellWidth = width;
362 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700363 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
364 mCountX, mCountY);
365 }
366
Adam Cohen2801caf2011-05-13 20:57:39 -0700367 public void setGridSize(int x, int y) {
368 mCountX = x;
369 mCountY = y;
370 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800371 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700372 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700373 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700374 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700375 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700376 }
377
Adam Cohen2374abf2013-04-16 14:56:57 -0700378 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
379 public void setInvertIfRtl(boolean invert) {
380 mShortcutsAndWidgets.setInvertIfRtl(invert);
381 }
382
Adam Cohen917e3882013-10-31 15:03:35 -0700383 public void setDropPending(boolean pending) {
384 mDropPending = pending;
385 }
386
387 public boolean isDropPending() {
388 return mDropPending;
389 }
390
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700391 @Override
392 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700393 if (icon == null || background == null) {
394 mTouchFeedbackView.setBitmap(null);
395 mTouchFeedbackView.animate().cancel();
396 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700397 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700398 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
399 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700400 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800401 }
402 }
403
Adam Cohenc50438c2014-08-19 17:43:05 -0700404 void disableDragTarget() {
405 mIsDragTarget = false;
406 }
407
408 boolean isDragTarget() {
409 return mIsDragTarget;
410 }
411
412 void setIsDragOverlapping(boolean isDragOverlapping) {
413 if (mIsDragOverlapping != isDragOverlapping) {
414 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700415 if (mIsDragOverlapping) {
416 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
417 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700418 if (mBackgroundAlpha > 0f) {
419 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
420 } else {
421 mBackground.resetTransition();
422 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700423 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700424 invalidate();
425 }
426 }
427
Michael Jurka33945b22010-12-21 18:19:38 -0800428 boolean getIsDragOverlapping() {
429 return mIsDragOverlapping;
430 }
431
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700432 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700433 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700434 if (!mIsDragTarget) {
435 return;
436 }
437
Michael Jurka3e7c7632010-10-02 16:01:03 -0700438 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
439 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
440 // When we're small, we are either drawn normally or in the "accepts drops" state (during
441 // a drag). However, we also drag the mini hover background *over* one of those two
442 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700443 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700444 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700445 }
Romain Guya6abce82009-11-10 02:54:41 -0800446
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700447 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700448 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700449 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700450 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800451 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700452 mTempRect.set(r);
453 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700454 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700455 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700456 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700457 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700458 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800459
Adam Cohen482ed822012-03-02 14:15:13 -0800460 if (DEBUG_VISUALIZE_OCCUPIED) {
461 int[] pt = new int[2];
462 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700463 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800464 for (int i = 0; i < mCountX; i++) {
465 for (int j = 0; j < mCountY; j++) {
466 if (mOccupied[i][j]) {
467 cellToPoint(i, j, pt);
468 canvas.save();
469 canvas.translate(pt[0], pt[1]);
470 cd.draw(canvas);
471 canvas.restore();
472 }
473 }
474 }
475 }
476
Andrew Flynn850d2e72012-04-26 16:51:20 -0700477 int previewOffset = FolderRingAnimator.sPreviewSize;
478
Adam Cohen69ce2e52011-07-03 19:25:21 -0700479 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700480 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700481 for (int i = 0; i < mFolderOuterRings.size(); i++) {
482 FolderRingAnimator fra = mFolderOuterRings.get(i);
483
Adam Cohen5108bc02013-09-20 17:04:51 -0700484 Drawable d;
485 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700486 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700487 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700488
Winson Chung89f97052013-09-20 11:32:26 -0700489 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700490 int centerX = mTempLocation[0] + mCellWidth / 2;
491 int centerY = mTempLocation[1] + previewOffset / 2 +
492 child.getPaddingTop() + grid.folderBackgroundOffset;
493
Adam Cohen5108bc02013-09-20 17:04:51 -0700494 // Draw outer ring, if it exists
495 if (FolderIcon.HAS_OUTER_RING) {
496 d = FolderRingAnimator.sSharedOuterRingDrawable;
497 width = (int) (fra.getOuterRingSize() * getChildrenScale());
498 height = width;
499 canvas.save();
500 canvas.translate(centerX - width / 2, centerY - height / 2);
501 d.setBounds(0, 0, width, height);
502 d.draw(canvas);
503 canvas.restore();
504 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700505
Winson Chung89f97052013-09-20 11:32:26 -0700506 // Draw inner ring
507 d = FolderRingAnimator.sSharedInnerRingDrawable;
508 width = (int) (fra.getInnerRingSize() * getChildrenScale());
509 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700510 canvas.save();
511 canvas.translate(centerX - width / 2, centerY - width / 2);
512 d.setBounds(0, 0, width, height);
513 d.draw(canvas);
514 canvas.restore();
515 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700516 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700517
518 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
519 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
520 int width = d.getIntrinsicWidth();
521 int height = d.getIntrinsicHeight();
522
523 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700524 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700525 if (child != null) {
526 int centerX = mTempLocation[0] + mCellWidth / 2;
527 int centerY = mTempLocation[1] + previewOffset / 2 +
528 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700529
Winson Chung89f97052013-09-20 11:32:26 -0700530 canvas.save();
531 canvas.translate(centerX - width / 2, centerY - width / 2);
532 d.setBounds(0, 0, width, height);
533 d.draw(canvas);
534 canvas.restore();
535 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700536 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700537 }
538
539 public void showFolderAccept(FolderRingAnimator fra) {
540 mFolderOuterRings.add(fra);
541 }
542
543 public void hideFolderAccept(FolderRingAnimator fra) {
544 if (mFolderOuterRings.contains(fra)) {
545 mFolderOuterRings.remove(fra);
546 }
547 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700548 }
549
Adam Cohenc51934b2011-07-26 21:07:43 -0700550 public void setFolderLeaveBehindCell(int x, int y) {
551 mFolderLeaveBehindCell[0] = x;
552 mFolderLeaveBehindCell[1] = y;
553 invalidate();
554 }
555
556 public void clearFolderLeaveBehind() {
557 mFolderLeaveBehindCell[0] = -1;
558 mFolderLeaveBehindCell[1] = -1;
559 invalidate();
560 }
561
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700562 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700563 public boolean shouldDelayChildPressedState() {
564 return false;
565 }
566
Adam Cohen1462de32012-07-24 22:34:36 -0700567 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700568 try {
569 dispatchRestoreInstanceState(states);
570 } catch (IllegalArgumentException ex) {
571 if (LauncherAppState.isDogfoodBuild()) {
572 throw ex;
573 }
574 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
575 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
576 }
Adam Cohen1462de32012-07-24 22:34:36 -0700577 }
578
Michael Jurkae6235dd2011-10-04 15:02:05 -0700579 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700580 public void cancelLongPress() {
581 super.cancelLongPress();
582
583 // Cancel long press for all children
584 final int count = getChildCount();
585 for (int i = 0; i < count; i++) {
586 final View child = getChildAt(i);
587 child.cancelLongPress();
588 }
589 }
590
Michael Jurkadee05892010-07-27 10:01:56 -0700591 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
592 mInterceptTouchListener = listener;
593 }
594
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800595 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700596 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800597 }
598
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800599 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700600 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800601 }
602
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800603 public void setIsHotseat(boolean isHotseat) {
604 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700605 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800606 }
607
Sunny Goyale9b651e2015-04-24 11:44:51 -0700608 public boolean isHotseat() {
609 return mIsHotseat;
610 }
611
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800612 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700613 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700614 final LayoutParams lp = params;
615
Andrew Flynnde38e422012-05-08 11:22:15 -0700616 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800617 if (child instanceof BubbleTextView) {
618 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700619 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800620 }
621
Adam Cohen307fe232012-08-16 17:55:58 -0700622 child.setScaleX(getChildrenScale());
623 child.setScaleY(getChildrenScale());
624
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800625 // Generate an id for each view, this assumes we have at most 256x256 cells
626 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700627 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700628 // If the horizontal or vertical span is set to -1, it is taken to
629 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700630 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
631 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800632
Winson Chungaafa03c2010-06-11 17:34:16 -0700633 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700634 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700635
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700636 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700637
Winson Chungaafa03c2010-06-11 17:34:16 -0700638 return true;
639 }
640 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800641 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700642
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800643 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700644 public void removeAllViews() {
645 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700646 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700647 }
648
649 @Override
650 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700651 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700652 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700653 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700654 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700655 }
656
657 @Override
658 public void removeView(View view) {
659 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700660 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700661 }
662
663 @Override
664 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700665 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
666 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700667 }
668
669 @Override
670 public void removeViewInLayout(View view) {
671 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700672 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700673 }
674
675 @Override
676 public void removeViews(int start, int count) {
677 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700678 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700680 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700681 }
682
683 @Override
684 public void removeViewsInLayout(int start, int count) {
685 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700686 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700687 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700688 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800689 }
690
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700691 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700692 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800693 * @param x X coordinate of the point
694 * @param y Y coordinate of the point
695 * @param result Array of 2 ints to hold the x and y coordinate of the cell
696 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700697 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700698 final int hStartPadding = getPaddingLeft();
699 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800700
701 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
702 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
703
Adam Cohend22015c2010-07-26 22:02:18 -0700704 final int xAxis = mCountX;
705 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800706
707 if (result[0] < 0) result[0] = 0;
708 if (result[0] >= xAxis) result[0] = xAxis - 1;
709 if (result[1] < 0) result[1] = 0;
710 if (result[1] >= yAxis) result[1] = yAxis - 1;
711 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700712
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800713 /**
714 * Given a point, return the cell that most closely encloses that point
715 * @param x X coordinate of the point
716 * @param y Y coordinate of the point
717 * @param result Array of 2 ints to hold the x and y coordinate of the cell
718 */
719 void pointToCellRounded(int x, int y, int[] result) {
720 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
721 }
722
723 /**
724 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700725 *
726 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800727 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700728 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800729 * @param result Array of 2 ints to hold the x and y coordinate of the point
730 */
731 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700732 final int hStartPadding = getPaddingLeft();
733 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800734
735 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
736 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
737 }
738
Adam Cohene3e27a82011-04-15 12:07:39 -0700739 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800740 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700741 *
742 * @param cellX X coordinate of the cell
743 * @param cellY Y coordinate of the cell
744 *
745 * @param result Array of 2 ints to hold the x and y coordinate of the point
746 */
747 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700748 regionToCenterPoint(cellX, cellY, 1, 1, result);
749 }
750
751 /**
752 * Given a cell coordinate and span return the point that represents the center of the regio
753 *
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 regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700760 final int hStartPadding = getPaddingLeft();
761 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700762 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
763 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
764 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
765 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700766 }
767
Adam Cohen19f37922012-03-21 11:59:11 -0700768 /**
769 * Given a cell coordinate and span fills out a corresponding pixel rect
770 *
771 * @param cellX X coordinate of the cell
772 * @param cellY Y coordinate of the cell
773 * @param result Rect in which to write the result
774 */
775 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
776 final int hStartPadding = getPaddingLeft();
777 final int vStartPadding = getPaddingTop();
778 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
779 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
780 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
781 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
782 }
783
Adam Cohen482ed822012-03-02 14:15:13 -0800784 public float getDistanceFromCell(float x, float y, int[] cell) {
785 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700786 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800787 }
788
Romain Guy84f296c2009-11-04 15:00:44 -0800789 int getCellWidth() {
790 return mCellWidth;
791 }
792
793 int getCellHeight() {
794 return mCellHeight;
795 }
796
Adam Cohend4844c32011-02-18 19:25:06 -0800797 int getWidthGap() {
798 return mWidthGap;
799 }
800
801 int getHeightGap() {
802 return mHeightGap;
803 }
804
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700805 public void setFixedSize(int width, int height) {
806 mFixedWidth = width;
807 mFixedHeight = height;
808 }
809
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800810 @Override
811 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800812 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800813 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700814 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
815 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700816 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
817 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700818 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700819 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
820 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700821 if (cw != mCellWidth || ch != mCellHeight) {
822 mCellWidth = cw;
823 mCellHeight = ch;
824 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
825 mHeightGap, mCountX, mCountY);
826 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700827 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700828
Winson Chung2d75f122013-09-23 16:53:31 -0700829 int newWidth = childWidthSize;
830 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700831 if (mFixedWidth > 0 && mFixedHeight > 0) {
832 newWidth = mFixedWidth;
833 newHeight = mFixedHeight;
834 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800835 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
836 }
837
Adam Cohend22015c2010-07-26 22:02:18 -0700838 int numWidthGaps = mCountX - 1;
839 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800840
Adam Cohen234c4cd2011-07-17 21:03:04 -0700841 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700842 int hSpace = childWidthSize;
843 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700844 int hFreeSpace = hSpace - (mCountX * mCellWidth);
845 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700846 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
847 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700848 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
849 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700850 } else {
851 mWidthGap = mOriginalWidthGap;
852 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700853 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700854
855 // Make the feedback view large enough to hold the blur bitmap.
856 mTouchFeedbackView.measure(
857 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
858 MeasureSpec.EXACTLY),
859 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
860 MeasureSpec.EXACTLY));
861
862 mShortcutsAndWidgets.measure(
863 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
864 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
865
866 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
867 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700868 if (mFixedWidth > 0 && mFixedHeight > 0) {
869 setMeasuredDimension(maxWidth, maxHeight);
870 } else {
871 setMeasuredDimension(widthSize, heightSize);
872 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800873 }
874
875 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700876 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700877 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
878 (mCountX * mCellWidth);
879 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
880 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700881
882 mTouchFeedbackView.layout(left, top,
883 left + mTouchFeedbackView.getMeasuredWidth(),
884 top + mTouchFeedbackView.getMeasuredHeight());
885 mShortcutsAndWidgets.layout(left, top,
886 left + r - l,
887 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800888 }
889
890 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700891 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
892 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700893
894 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700895 mBackground.getPadding(mTempRect);
896 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
897 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700898 }
899
900 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800901 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700902 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800903 }
904
905 @Override
906 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700907 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908 }
909
Michael Jurka5f1c5092010-09-03 14:15:02 -0700910 public float getBackgroundAlpha() {
911 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700912 }
913
Michael Jurka5f1c5092010-09-03 14:15:02 -0700914 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800915 if (mBackgroundAlpha != alpha) {
916 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700917 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800918 }
Michael Jurkadee05892010-07-27 10:01:56 -0700919 }
920
Sunny Goyal2805e632015-05-20 15:35:32 -0700921 @Override
922 protected boolean verifyDrawable(Drawable who) {
923 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
924 }
925
Michael Jurkaa52570f2012-03-20 03:18:20 -0700926 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700927 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700928 }
929
Michael Jurkaa52570f2012-03-20 03:18:20 -0700930 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700931 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700932 }
933
Patrick Dubroy440c3602010-07-13 17:50:32 -0700934 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700935 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700936 }
937
Adam Cohen76fc0852011-06-17 13:26:23 -0700938 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800939 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700940 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800941 boolean[][] occupied = mOccupied;
942 if (!permanent) {
943 occupied = mTmpOccupied;
944 }
945
Adam Cohen19f37922012-03-21 11:59:11 -0700946 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700947 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
948 final ItemInfo info = (ItemInfo) child.getTag();
949
950 // We cancel any existing animations
951 if (mReorderAnimators.containsKey(lp)) {
952 mReorderAnimators.get(lp).cancel();
953 mReorderAnimators.remove(lp);
954 }
955
Adam Cohen482ed822012-03-02 14:15:13 -0800956 final int oldX = lp.x;
957 final int oldY = lp.y;
958 if (adjustOccupied) {
959 occupied[lp.cellX][lp.cellY] = false;
960 occupied[cellX][cellY] = true;
961 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700962 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800963 if (permanent) {
964 lp.cellX = info.cellX = cellX;
965 lp.cellY = info.cellY = cellY;
966 } else {
967 lp.tmpCellX = cellX;
968 lp.tmpCellY = cellY;
969 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700970 clc.setupLp(lp);
971 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800972 final int newX = lp.x;
973 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700974
Adam Cohen76fc0852011-06-17 13:26:23 -0700975 lp.x = oldX;
976 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700977
Adam Cohen482ed822012-03-02 14:15:13 -0800978 // Exit early if we're not actually moving the view
979 if (oldX == newX && oldY == newY) {
980 lp.isLockedToGrid = true;
981 return true;
982 }
983
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100984 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800985 va.setDuration(duration);
986 mReorderAnimators.put(lp, va);
987
988 va.addUpdateListener(new AnimatorUpdateListener() {
989 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700990 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -0800991 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -0700992 lp.x = (int) ((1 - r) * oldX + r * newX);
993 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -0700994 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700995 }
996 });
Adam Cohen482ed822012-03-02 14:15:13 -0800997 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700998 boolean cancelled = false;
999 public void onAnimationEnd(Animator animation) {
1000 // If the animation was cancelled, it means that another animation
1001 // has interrupted this one, and we don't want to lock the item into
1002 // place just yet.
1003 if (!cancelled) {
1004 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001005 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001006 }
1007 if (mReorderAnimators.containsKey(lp)) {
1008 mReorderAnimators.remove(lp);
1009 }
1010 }
1011 public void onAnimationCancel(Animator animation) {
1012 cancelled = true;
1013 }
1014 });
Adam Cohen482ed822012-03-02 14:15:13 -08001015 va.setStartDelay(delay);
1016 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001017 return true;
1018 }
1019 return false;
1020 }
1021
Adam Cohen482ed822012-03-02 14:15:13 -08001022 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1023 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001024 final int oldDragCellX = mDragCell[0];
1025 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001026
Adam Cohen2801caf2011-05-13 20:57:39 -07001027 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001028 return;
1029 }
1030
Adam Cohen482ed822012-03-02 14:15:13 -08001031 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1032 mDragCell[0] = cellX;
1033 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001034 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001035 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001036 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001037
Joe Onorato4be866d2010-10-10 11:26:02 -07001038 int left = topLeft[0];
1039 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001040
Winson Chungb8c69f32011-10-19 21:36:08 -07001041 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001042 // When drawing the drag outline, it did not account for margin offsets
1043 // added by the view's parent.
1044 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1045 left += lp.leftMargin;
1046 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001047
Adam Cohen99e8b402011-03-25 19:23:43 -07001048 // Offsets due to the size difference between the View and the dragOutline.
1049 // There is a size difference to account for the outer blur, which may lie
1050 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001051 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001052 // We center about the x axis
1053 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1054 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001055 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001056 if (dragOffset != null && dragRegion != null) {
1057 // Center the drag region *horizontally* in the cell and apply a drag
1058 // outline offset
1059 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1060 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001061 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1062 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1063 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001064 } else {
1065 // Center the drag outline in the cell
1066 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1067 - dragOutline.getWidth()) / 2;
1068 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1069 - dragOutline.getHeight()) / 2;
1070 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001071 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001072 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001073 mDragOutlineAnims[oldIndex].animateOut();
1074 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001075 Rect r = mDragOutlines[mDragOutlineCurrent];
1076 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1077 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001078 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001079 }
Winson Chung150fbab2010-09-29 17:14:26 -07001080
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001081 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1082 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001083 }
1084 }
1085
Adam Cohene0310962011-04-18 16:15:31 -07001086 public void clearDragOutlines() {
1087 final int oldIndex = mDragOutlineCurrent;
1088 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001089 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001090 }
1091
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001092 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001093 * Find a vacant area that will fit the given bounds nearest the requested
1094 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001095 *
Romain Guy51afc022009-05-04 18:03:43 -07001096 * @param pixelX The X location at which you want to search for a vacant area.
1097 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001098 * @param spanX Horizontal span of the object.
1099 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001100 * @param result Array in which to place the result, or null (in which case a new array will
1101 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001102 * @return The X, Y cell of a vacant area that can contain this object,
1103 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001104 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001105 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1106 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001107 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001108
Michael Jurka6a1435d2010-09-27 17:35:12 -07001109 /**
1110 * Find a vacant area that will fit the given bounds nearest the requested
1111 * cell location. Uses Euclidean distance to score multiple vacant areas.
1112 *
1113 * @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.
Adam Cohend41fbf52012-02-16 23:53:59 -08001115 * @param minSpanX The minimum horizontal span required
1116 * @param minSpanY The minimum vertical span required
1117 * @param spanX Horizontal span of the object.
1118 * @param spanY Vertical span of the object.
1119 * @param result Array in which to place the result, or null (in which case a new array will
1120 * be allocated)
1121 * @return The X, Y cell of a vacant area that can contain this object,
1122 * nearest the requested location.
1123 */
1124 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1125 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001126 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001127 result, resultSpan);
1128 }
1129
Adam Cohend41fbf52012-02-16 23:53:59 -08001130 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1131 private void lazyInitTempRectStack() {
1132 if (mTempRectStack.isEmpty()) {
1133 for (int i = 0; i < mCountX * mCountY; i++) {
1134 mTempRectStack.push(new Rect());
1135 }
1136 }
1137 }
Adam Cohen482ed822012-03-02 14:15:13 -08001138
Adam Cohend41fbf52012-02-16 23:53:59 -08001139 private void recycleTempRects(Stack<Rect> used) {
1140 while (!used.isEmpty()) {
1141 mTempRectStack.push(used.pop());
1142 }
1143 }
1144
1145 /**
1146 * Find a vacant area that will fit the given bounds nearest the requested
1147 * cell location. Uses Euclidean distance to score multiple vacant areas.
1148 *
1149 * @param pixelX The X location at which you want to search for a vacant area.
1150 * @param pixelY The Y location at which you want to search for a vacant area.
1151 * @param minSpanX The minimum horizontal span required
1152 * @param minSpanY The minimum vertical span required
1153 * @param spanX Horizontal span of the object.
1154 * @param spanY Vertical span of the object.
1155 * @param ignoreOccupied If true, the result can be an occupied cell
1156 * @param result Array in which to place the result, or null (in which case a new array will
1157 * be allocated)
1158 * @return The X, Y cell of a vacant area that can contain this object,
1159 * nearest the requested location.
1160 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001161 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1162 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001163 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001164
Adam Cohene3e27a82011-04-15 12:07:39 -07001165 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1166 // to the center of the item, but we are searching based on the top-left cell, so
1167 // we translate the point over to correspond to the top-left.
1168 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1169 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1170
Jeff Sharkey70864282009-04-07 21:08:40 -07001171 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001172 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001173 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001174 final Rect bestRect = new Rect(-1, -1, -1, -1);
1175 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001176
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001177 final int countX = mCountX;
1178 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001179
Adam Cohend41fbf52012-02-16 23:53:59 -08001180 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1181 spanX < minSpanX || spanY < minSpanY) {
1182 return bestXY;
1183 }
1184
1185 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001186 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001187 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1188 int ySize = -1;
1189 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001190 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001191 // First, let's see if this thing fits anywhere
1192 for (int i = 0; i < minSpanX; i++) {
1193 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001194 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001195 continue inner;
1196 }
Michael Jurkac28de512010-08-13 11:27:44 -07001197 }
1198 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001199 xSize = minSpanX;
1200 ySize = minSpanY;
1201
1202 // We know that the item will fit at _some_ acceptable size, now let's see
1203 // how big we can make it. We'll alternate between incrementing x and y spans
1204 // until we hit a limit.
1205 boolean incX = true;
1206 boolean hitMaxX = xSize >= spanX;
1207 boolean hitMaxY = ySize >= spanY;
1208 while (!(hitMaxX && hitMaxY)) {
1209 if (incX && !hitMaxX) {
1210 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001211 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001212 // We can't move out horizontally
1213 hitMaxX = true;
1214 }
1215 }
1216 if (!hitMaxX) {
1217 xSize++;
1218 }
1219 } else if (!hitMaxY) {
1220 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001221 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001222 // We can't move out vertically
1223 hitMaxY = true;
1224 }
1225 }
1226 if (!hitMaxY) {
1227 ySize++;
1228 }
1229 }
1230 hitMaxX |= xSize >= spanX;
1231 hitMaxY |= ySize >= spanY;
1232 incX = !incX;
1233 }
1234 incX = true;
1235 hitMaxX = xSize >= spanX;
1236 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001237 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001238 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001239 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001240
Adam Cohend41fbf52012-02-16 23:53:59 -08001241 // We verify that the current rect is not a sub-rect of any of our previous
1242 // candidates. In this case, the current rect is disqualified in favour of the
1243 // containing rect.
1244 Rect currentRect = mTempRectStack.pop();
1245 currentRect.set(x, y, x + xSize, y + ySize);
1246 boolean contained = false;
1247 for (Rect r : validRegions) {
1248 if (r.contains(currentRect)) {
1249 contained = true;
1250 break;
1251 }
1252 }
1253 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001254 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001255
Adam Cohend41fbf52012-02-16 23:53:59 -08001256 if ((distance <= bestDistance && !contained) ||
1257 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001258 bestDistance = distance;
1259 bestXY[0] = x;
1260 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001261 if (resultSpan != null) {
1262 resultSpan[0] = xSize;
1263 resultSpan[1] = ySize;
1264 }
1265 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001266 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001267 }
1268 }
1269
Adam Cohenc0dcf592011-06-01 15:30:43 -07001270 // Return -1, -1 if no suitable location found
1271 if (bestDistance == Double.MAX_VALUE) {
1272 bestXY[0] = -1;
1273 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001274 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001275 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001276 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001277 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001278
Adam Cohen482ed822012-03-02 14:15:13 -08001279 /**
1280 * Find a vacant area that will fit the given bounds nearest the requested
1281 * cell location, and will also weigh in a suggested direction vector of the
1282 * desired location. This method computers distance based on unit grid distances,
1283 * not pixel distances.
1284 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001285 * @param cellX The X cell nearest to which you want to search for a vacant area.
1286 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001287 * @param spanX Horizontal span of the object.
1288 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001289 * @param direction The favored direction in which the views should move from x, y
1290 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1291 * matches exactly. Otherwise we find the best matching direction.
1292 * @param occoupied The array which represents which cells in the CellLayout are occupied
1293 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001294 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001295 * @param result Array in which to place the result, or null (in which case a new array will
1296 * be allocated)
1297 * @return The X, Y cell of a vacant area that can contain this object,
1298 * nearest the requested location.
1299 */
1300 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001301 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001302 // Keep track of best-scoring drop area
1303 final int[] bestXY = result != null ? result : new int[2];
1304 float bestDistance = Float.MAX_VALUE;
1305 int bestDirectionScore = Integer.MIN_VALUE;
1306
1307 final int countX = mCountX;
1308 final int countY = mCountY;
1309
1310 for (int y = 0; y < countY - (spanY - 1); y++) {
1311 inner:
1312 for (int x = 0; x < countX - (spanX - 1); x++) {
1313 // First, let's see if this thing fits anywhere
1314 for (int i = 0; i < spanX; i++) {
1315 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001316 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001317 continue inner;
1318 }
1319 }
1320 }
1321
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001322 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001323 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001324 computeDirectionVector(x - cellX, y - cellY, curDirection);
1325 // The direction score is just the dot product of the two candidate direction
1326 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001327 int curDirectionScore = direction[0] * curDirection[0] +
1328 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001329 boolean exactDirectionOnly = false;
1330 boolean directionMatches = direction[0] == curDirection[0] &&
1331 direction[0] == curDirection[0];
1332 if ((directionMatches || !exactDirectionOnly) &&
1333 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001334 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1335 bestDistance = distance;
1336 bestDirectionScore = curDirectionScore;
1337 bestXY[0] = x;
1338 bestXY[1] = y;
1339 }
1340 }
1341 }
1342
1343 // Return -1, -1 if no suitable location found
1344 if (bestDistance == Float.MAX_VALUE) {
1345 bestXY[0] = -1;
1346 bestXY[1] = -1;
1347 }
1348 return bestXY;
1349 }
1350
1351 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001352 int[] direction, ItemConfiguration currentState) {
1353 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001354 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001355 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001356 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1357
Adam Cohen8baab352012-03-20 17:39:21 -07001358 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001359
1360 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001361 c.x = mTempLocation[0];
1362 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001363 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001364 }
Adam Cohen8baab352012-03-20 17:39:21 -07001365 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001366 return success;
1367 }
1368
Adam Cohenf3900c22012-11-16 18:28:11 -08001369 /**
1370 * This helper class defines a cluster of views. It helps with defining complex edges
1371 * of the cluster and determining how those edges interact with other views. The edges
1372 * essentially define a fine-grained boundary around the cluster of views -- like a more
1373 * precise version of a bounding box.
1374 */
1375 private class ViewCluster {
1376 final static int LEFT = 0;
1377 final static int TOP = 1;
1378 final static int RIGHT = 2;
1379 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001380
Adam Cohenf3900c22012-11-16 18:28:11 -08001381 ArrayList<View> views;
1382 ItemConfiguration config;
1383 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001384
Adam Cohenf3900c22012-11-16 18:28:11 -08001385 int[] leftEdge = new int[mCountY];
1386 int[] rightEdge = new int[mCountY];
1387 int[] topEdge = new int[mCountX];
1388 int[] bottomEdge = new int[mCountX];
1389 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1390
1391 @SuppressWarnings("unchecked")
1392 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1393 this.views = (ArrayList<View>) views.clone();
1394 this.config = config;
1395 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001396 }
1397
Adam Cohenf3900c22012-11-16 18:28:11 -08001398 void resetEdges() {
1399 for (int i = 0; i < mCountX; i++) {
1400 topEdge[i] = -1;
1401 bottomEdge[i] = -1;
1402 }
1403 for (int i = 0; i < mCountY; i++) {
1404 leftEdge[i] = -1;
1405 rightEdge[i] = -1;
1406 }
1407 leftEdgeDirty = true;
1408 rightEdgeDirty = true;
1409 bottomEdgeDirty = true;
1410 topEdgeDirty = true;
1411 boundingRectDirty = true;
1412 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001413
Adam Cohenf3900c22012-11-16 18:28:11 -08001414 void computeEdge(int which, int[] edge) {
1415 int count = views.size();
1416 for (int i = 0; i < count; i++) {
1417 CellAndSpan cs = config.map.get(views.get(i));
1418 switch (which) {
1419 case LEFT:
1420 int left = cs.x;
1421 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1422 if (left < edge[j] || edge[j] < 0) {
1423 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001424 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001425 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001426 break;
1427 case RIGHT:
1428 int right = cs.x + cs.spanX;
1429 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1430 if (right > edge[j]) {
1431 edge[j] = right;
1432 }
1433 }
1434 break;
1435 case TOP:
1436 int top = cs.y;
1437 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1438 if (top < edge[j] || edge[j] < 0) {
1439 edge[j] = top;
1440 }
1441 }
1442 break;
1443 case BOTTOM:
1444 int bottom = cs.y + cs.spanY;
1445 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1446 if (bottom > edge[j]) {
1447 edge[j] = bottom;
1448 }
1449 }
1450 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001451 }
1452 }
1453 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001454
1455 boolean isViewTouchingEdge(View v, int whichEdge) {
1456 CellAndSpan cs = config.map.get(v);
1457
1458 int[] edge = getEdge(whichEdge);
1459
1460 switch (whichEdge) {
1461 case LEFT:
1462 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1463 if (edge[i] == cs.x + cs.spanX) {
1464 return true;
1465 }
1466 }
1467 break;
1468 case RIGHT:
1469 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1470 if (edge[i] == cs.x) {
1471 return true;
1472 }
1473 }
1474 break;
1475 case TOP:
1476 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1477 if (edge[i] == cs.y + cs.spanY) {
1478 return true;
1479 }
1480 }
1481 break;
1482 case BOTTOM:
1483 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1484 if (edge[i] == cs.y) {
1485 return true;
1486 }
1487 }
1488 break;
1489 }
1490 return false;
1491 }
1492
1493 void shift(int whichEdge, int delta) {
1494 for (View v: views) {
1495 CellAndSpan c = config.map.get(v);
1496 switch (whichEdge) {
1497 case LEFT:
1498 c.x -= delta;
1499 break;
1500 case RIGHT:
1501 c.x += delta;
1502 break;
1503 case TOP:
1504 c.y -= delta;
1505 break;
1506 case BOTTOM:
1507 default:
1508 c.y += delta;
1509 break;
1510 }
1511 }
1512 resetEdges();
1513 }
1514
1515 public void addView(View v) {
1516 views.add(v);
1517 resetEdges();
1518 }
1519
1520 public Rect getBoundingRect() {
1521 if (boundingRectDirty) {
1522 boolean first = true;
1523 for (View v: views) {
1524 CellAndSpan c = config.map.get(v);
1525 if (first) {
1526 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1527 first = false;
1528 } else {
1529 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1530 }
1531 }
1532 }
1533 return boundingRect;
1534 }
1535
1536 public int[] getEdge(int which) {
1537 switch (which) {
1538 case LEFT:
1539 return getLeftEdge();
1540 case RIGHT:
1541 return getRightEdge();
1542 case TOP:
1543 return getTopEdge();
1544 case BOTTOM:
1545 default:
1546 return getBottomEdge();
1547 }
1548 }
1549
1550 public int[] getLeftEdge() {
1551 if (leftEdgeDirty) {
1552 computeEdge(LEFT, leftEdge);
1553 }
1554 return leftEdge;
1555 }
1556
1557 public int[] getRightEdge() {
1558 if (rightEdgeDirty) {
1559 computeEdge(RIGHT, rightEdge);
1560 }
1561 return rightEdge;
1562 }
1563
1564 public int[] getTopEdge() {
1565 if (topEdgeDirty) {
1566 computeEdge(TOP, topEdge);
1567 }
1568 return topEdge;
1569 }
1570
1571 public int[] getBottomEdge() {
1572 if (bottomEdgeDirty) {
1573 computeEdge(BOTTOM, bottomEdge);
1574 }
1575 return bottomEdge;
1576 }
1577
1578 PositionComparator comparator = new PositionComparator();
1579 class PositionComparator implements Comparator<View> {
1580 int whichEdge = 0;
1581 public int compare(View left, View right) {
1582 CellAndSpan l = config.map.get(left);
1583 CellAndSpan r = config.map.get(right);
1584 switch (whichEdge) {
1585 case LEFT:
1586 return (r.x + r.spanX) - (l.x + l.spanX);
1587 case RIGHT:
1588 return l.x - r.x;
1589 case TOP:
1590 return (r.y + r.spanY) - (l.y + l.spanY);
1591 case BOTTOM:
1592 default:
1593 return l.y - r.y;
1594 }
1595 }
1596 }
1597
1598 public void sortConfigurationForEdgePush(int edge) {
1599 comparator.whichEdge = edge;
1600 Collections.sort(config.sortedViews, comparator);
1601 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001602 }
1603
Adam Cohenf3900c22012-11-16 18:28:11 -08001604 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1605 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001606
Adam Cohenf3900c22012-11-16 18:28:11 -08001607 ViewCluster cluster = new ViewCluster(views, currentState);
1608 Rect clusterRect = cluster.getBoundingRect();
1609 int whichEdge;
1610 int pushDistance;
1611 boolean fail = false;
1612
1613 // Determine the edge of the cluster that will be leading the push and how far
1614 // the cluster must be shifted.
1615 if (direction[0] < 0) {
1616 whichEdge = ViewCluster.LEFT;
1617 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001618 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001619 whichEdge = ViewCluster.RIGHT;
1620 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1621 } else if (direction[1] < 0) {
1622 whichEdge = ViewCluster.TOP;
1623 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1624 } else {
1625 whichEdge = ViewCluster.BOTTOM;
1626 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001627 }
1628
Adam Cohenf3900c22012-11-16 18:28:11 -08001629 // Break early for invalid push distance.
1630 if (pushDistance <= 0) {
1631 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001632 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001633
1634 // Mark the occupied state as false for the group of views we want to move.
1635 for (View v: views) {
1636 CellAndSpan c = currentState.map.get(v);
1637 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1638 }
1639
1640 // We save the current configuration -- if we fail to find a solution we will revert
1641 // to the initial state. The process of finding a solution modifies the configuration
1642 // in place, hence the need for revert in the failure case.
1643 currentState.save();
1644
1645 // The pushing algorithm is simplified by considering the views in the order in which
1646 // they would be pushed by the cluster. For example, if the cluster is leading with its
1647 // left edge, we consider sort the views by their right edge, from right to left.
1648 cluster.sortConfigurationForEdgePush(whichEdge);
1649
1650 while (pushDistance > 0 && !fail) {
1651 for (View v: currentState.sortedViews) {
1652 // For each view that isn't in the cluster, we see if the leading edge of the
1653 // cluster is contacting the edge of that view. If so, we add that view to the
1654 // cluster.
1655 if (!cluster.views.contains(v) && v != dragView) {
1656 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1657 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1658 if (!lp.canReorder) {
1659 // The push solution includes the all apps button, this is not viable.
1660 fail = true;
1661 break;
1662 }
1663 cluster.addView(v);
1664 CellAndSpan c = currentState.map.get(v);
1665
1666 // Adding view to cluster, mark it as not occupied.
1667 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1668 }
1669 }
1670 }
1671 pushDistance--;
1672
1673 // The cluster has been completed, now we move the whole thing over in the appropriate
1674 // direction.
1675 cluster.shift(whichEdge, 1);
1676 }
1677
1678 boolean foundSolution = false;
1679 clusterRect = cluster.getBoundingRect();
1680
1681 // Due to the nature of the algorithm, the only check required to verify a valid solution
1682 // is to ensure that completed shifted cluster lies completely within the cell layout.
1683 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1684 clusterRect.bottom <= mCountY) {
1685 foundSolution = true;
1686 } else {
1687 currentState.restore();
1688 }
1689
1690 // In either case, we set the occupied array as marked for the location of the views
1691 for (View v: cluster.views) {
1692 CellAndSpan c = currentState.map.get(v);
1693 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1694 }
1695
1696 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001697 }
1698
Adam Cohen482ed822012-03-02 14:15:13 -08001699 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001700 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001701 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001702
Adam Cohen8baab352012-03-20 17:39:21 -07001703 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001704 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001705 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001706 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001707 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001708 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001709 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001710 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001711 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001712 }
1713 }
Adam Cohen8baab352012-03-20 17:39:21 -07001714
Adam Cohen8baab352012-03-20 17:39:21 -07001715 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001716 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001717 CellAndSpan c = currentState.map.get(v);
1718 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1719 }
1720
Adam Cohen47a876d2012-03-19 13:21:41 -07001721 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1722 int top = boundingRect.top;
1723 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001724 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001725 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001726 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001727 CellAndSpan c = currentState.map.get(v);
1728 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001729 }
1730
Adam Cohen482ed822012-03-02 14:15:13 -08001731 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1732
Adam Cohenf3900c22012-11-16 18:28:11 -08001733 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1734 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001735
Adam Cohen8baab352012-03-20 17:39:21 -07001736 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001737 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001738 int deltaX = mTempLocation[0] - boundingRect.left;
1739 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001740 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001741 CellAndSpan c = currentState.map.get(v);
1742 c.x += deltaX;
1743 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001744 }
1745 success = true;
1746 }
Adam Cohen8baab352012-03-20 17:39:21 -07001747
1748 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001749 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001750 CellAndSpan c = currentState.map.get(v);
1751 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001752 }
1753 return success;
1754 }
1755
1756 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1757 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1758 }
1759
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001760 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1761 // to push items in each of the cardinal directions, in an order based on the direction vector
1762 // passed.
1763 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1764 int[] direction, View ignoreView, ItemConfiguration solution) {
1765 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001766 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001767 // separately in each of the components.
1768 int temp = direction[1];
1769 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001770
1771 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001772 ignoreView, solution)) {
1773 return true;
1774 }
1775 direction[1] = temp;
1776 temp = direction[0];
1777 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001778
1779 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001780 ignoreView, solution)) {
1781 return true;
1782 }
1783 // Revert the direction
1784 direction[0] = temp;
1785
1786 // Now we try pushing in each component of the opposite direction
1787 direction[0] *= -1;
1788 direction[1] *= -1;
1789 temp = direction[1];
1790 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001791 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001792 ignoreView, solution)) {
1793 return true;
1794 }
1795
1796 direction[1] = temp;
1797 temp = direction[0];
1798 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001799 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001800 ignoreView, solution)) {
1801 return true;
1802 }
1803 // revert the direction
1804 direction[0] = temp;
1805 direction[0] *= -1;
1806 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001807
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001808 } else {
1809 // If the direction vector has a single non-zero component, we push first in the
1810 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001811 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001812 ignoreView, solution)) {
1813 return true;
1814 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001815 // Then we try the opposite direction
1816 direction[0] *= -1;
1817 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001818 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001819 ignoreView, solution)) {
1820 return true;
1821 }
1822 // Switch the direction back
1823 direction[0] *= -1;
1824 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001825
1826 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001827 // to find a solution by pushing along the perpendicular axis.
1828
1829 // Swap the components
1830 int temp = direction[1];
1831 direction[1] = direction[0];
1832 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001833 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001834 ignoreView, solution)) {
1835 return true;
1836 }
1837
1838 // Then we try the opposite direction
1839 direction[0] *= -1;
1840 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001841 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001842 ignoreView, solution)) {
1843 return true;
1844 }
1845 // Switch the direction back
1846 direction[0] *= -1;
1847 direction[1] *= -1;
1848
1849 // Swap the components back
1850 temp = direction[1];
1851 direction[1] = direction[0];
1852 direction[0] = temp;
1853 }
1854 return false;
1855 }
1856
Adam Cohen482ed822012-03-02 14:15:13 -08001857 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001858 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001859 // Return early if get invalid cell positions
1860 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001861
Adam Cohen8baab352012-03-20 17:39:21 -07001862 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001863 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001864
Adam Cohen8baab352012-03-20 17:39:21 -07001865 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001866 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001867 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001868 if (c != null) {
1869 c.x = cellX;
1870 c.y = cellY;
1871 }
Adam Cohen482ed822012-03-02 14:15:13 -08001872 }
Adam Cohen482ed822012-03-02 14:15:13 -08001873 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1874 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001875 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001876 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001877 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001878 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001879 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001880 if (Rect.intersects(r0, r1)) {
1881 if (!lp.canReorder) {
1882 return false;
1883 }
1884 mIntersectingViews.add(child);
1885 }
1886 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001887
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001888 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1889
Winson Chung5f8afe62013-08-12 16:19:28 -07001890 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001891 // we try to find a solution such that no displaced item travels through another item
1892 // without also displacing that item.
1893 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001894 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001895 return true;
1896 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001897
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001898 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001899 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001900 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001901 return true;
1902 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001903
Adam Cohen482ed822012-03-02 14:15:13 -08001904 // Ok, they couldn't move as a block, let's move them individually
1905 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001906 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001907 return false;
1908 }
1909 }
1910 return true;
1911 }
1912
1913 /*
1914 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1915 * the provided point and the provided cell
1916 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001917 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001918 double angle = Math.atan(((float) deltaY) / deltaX);
1919
1920 result[0] = 0;
1921 result[1] = 0;
1922 if (Math.abs(Math.cos(angle)) > 0.5f) {
1923 result[0] = (int) Math.signum(deltaX);
1924 }
1925 if (Math.abs(Math.sin(angle)) > 0.5f) {
1926 result[1] = (int) Math.signum(deltaY);
1927 }
1928 }
1929
Adam Cohen8baab352012-03-20 17:39:21 -07001930 private void copyOccupiedArray(boolean[][] occupied) {
1931 for (int i = 0; i < mCountX; i++) {
1932 for (int j = 0; j < mCountY; j++) {
1933 occupied[i][j] = mOccupied[i][j];
1934 }
1935 }
1936 }
1937
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001938 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001939 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1940 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001941 // Copy the current state into the solution. This solution will be manipulated as necessary.
1942 copyCurrentStateToSolution(solution, false);
1943 // Copy the current occupied array into the temporary occupied array. This array will be
1944 // manipulated as necessary to find a solution.
1945 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001946
1947 // We find the nearest cell into which we would place the dragged item, assuming there's
1948 // nothing in its way.
1949 int result[] = new int[2];
1950 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1951
1952 boolean success = false;
1953 // First we try the exact nearest position of the item being dragged,
1954 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001955 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1956 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001957
1958 if (!success) {
1959 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1960 // x, then 1 in y etc.
1961 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001962 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1963 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001964 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001965 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1966 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001967 }
1968 solution.isSolution = false;
1969 } else {
1970 solution.isSolution = true;
1971 solution.dragViewX = result[0];
1972 solution.dragViewY = result[1];
1973 solution.dragViewSpanX = spanX;
1974 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001975 }
1976 return solution;
1977 }
1978
1979 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001980 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001981 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001982 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001983 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001984 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001985 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001986 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001987 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001988 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001989 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001990 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001991 }
1992 }
1993
1994 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1995 for (int i = 0; i < mCountX; i++) {
1996 for (int j = 0; j < mCountY; j++) {
1997 mTmpOccupied[i][j] = false;
1998 }
1999 }
2000
Michael Jurkaa52570f2012-03-20 03:18:20 -07002001 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002002 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002003 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002004 if (child == dragView) continue;
2005 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002006 CellAndSpan c = solution.map.get(child);
2007 if (c != null) {
2008 lp.tmpCellX = c.x;
2009 lp.tmpCellY = c.y;
2010 lp.cellHSpan = c.spanX;
2011 lp.cellVSpan = c.spanY;
2012 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002013 }
2014 }
2015 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2016 solution.dragViewSpanY, mTmpOccupied, true);
2017 }
2018
2019 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2020 commitDragView) {
2021
2022 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2023 for (int i = 0; i < mCountX; i++) {
2024 for (int j = 0; j < mCountY; j++) {
2025 occupied[i][j] = false;
2026 }
2027 }
2028
Michael Jurkaa52570f2012-03-20 03:18:20 -07002029 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002030 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002031 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002032 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002033 CellAndSpan c = solution.map.get(child);
2034 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002035 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2036 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002037 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002038 }
2039 }
2040 if (commitDragView) {
2041 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2042 solution.dragViewSpanY, occupied, true);
2043 }
2044 }
2045
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002046
2047 // This method starts or changes the reorder preview animations
2048 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2049 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002050 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002051 for (int i = 0; i < childCount; i++) {
2052 View child = mShortcutsAndWidgets.getChildAt(i);
2053 if (child == dragView) continue;
2054 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002055 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2056 != null && !solution.intersectingViews.contains(child);
2057
Adam Cohen19f37922012-03-21 11:59:11 -07002058 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002059 if (c != null && !skip) {
2060 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2061 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002062 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002063 }
2064 }
2065 }
2066
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002067 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002068 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002069 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002070 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002071 float finalDeltaX;
2072 float finalDeltaY;
2073 float initDeltaX;
2074 float initDeltaY;
2075 float finalScale;
2076 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002077 int mode;
2078 boolean repeating = false;
2079 private static final int PREVIEW_DURATION = 300;
2080 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2081
2082 public static final int MODE_HINT = 0;
2083 public static final int MODE_PREVIEW = 1;
2084
Adam Cohene7587d22012-05-24 18:50:02 -07002085 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002086
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002087 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2088 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002089 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2090 final int x0 = mTmpPoint[0];
2091 final int y0 = mTmpPoint[1];
2092 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2093 final int x1 = mTmpPoint[0];
2094 final int y1 = mTmpPoint[1];
2095 final int dX = x1 - x0;
2096 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002097 finalDeltaX = 0;
2098 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002099 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002100 if (dX == dY && dX == 0) {
2101 } else {
2102 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002103 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002104 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002105 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002106 } else {
2107 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002108 finalDeltaX = (int) (- dir * Math.signum(dX) *
2109 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2110 finalDeltaY = (int) (- dir * Math.signum(dY) *
2111 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002112 }
2113 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002114 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002115 initDeltaX = child.getTranslationX();
2116 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002117 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002118 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002119 this.child = child;
2120 }
2121
Adam Cohend024f982012-05-23 18:26:45 -07002122 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002123 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002124 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002125 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002126 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002127 if (finalDeltaX == 0 && finalDeltaY == 0) {
2128 completeAnimationImmediately();
2129 return;
2130 }
Adam Cohen19f37922012-03-21 11:59:11 -07002131 }
Adam Cohend024f982012-05-23 18:26:45 -07002132 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002133 return;
2134 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002135 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002136 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002137 va.setRepeatMode(ValueAnimator.REVERSE);
2138 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002139 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002140 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002141 va.addUpdateListener(new AnimatorUpdateListener() {
2142 @Override
2143 public void onAnimationUpdate(ValueAnimator animation) {
2144 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002145 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2146 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2147 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002148 child.setTranslationX(x);
2149 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002150 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002151 child.setScaleX(s);
2152 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002153 }
2154 });
2155 va.addListener(new AnimatorListenerAdapter() {
2156 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002157 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002158 initDeltaX = 0;
2159 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002160 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002161 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002162 }
2163 });
Adam Cohen19f37922012-03-21 11:59:11 -07002164 mShakeAnimators.put(child, this);
2165 va.start();
2166 }
2167
Adam Cohend024f982012-05-23 18:26:45 -07002168 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002169 if (a != null) {
2170 a.cancel();
2171 }
Adam Cohen19f37922012-03-21 11:59:11 -07002172 }
Adam Cohene7587d22012-05-24 18:50:02 -07002173
Adam Cohen091440a2015-03-18 14:16:05 -07002174 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002175 if (a != null) {
2176 a.cancel();
2177 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002178
Michael Jurka2ecf9952012-06-18 12:52:28 -07002179 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002180 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002181 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002182 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2183 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002184 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2185 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002186 );
2187 s.setDuration(REORDER_ANIMATION_DURATION);
2188 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2189 s.start();
2190 }
Adam Cohen19f37922012-03-21 11:59:11 -07002191 }
2192
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002193 private void completeAndClearReorderPreviewAnimations() {
2194 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002195 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002196 }
2197 mShakeAnimators.clear();
2198 }
2199
Adam Cohen482ed822012-03-02 14:15:13 -08002200 private void commitTempPlacement() {
2201 for (int i = 0; i < mCountX; i++) {
2202 for (int j = 0; j < mCountY; j++) {
2203 mOccupied[i][j] = mTmpOccupied[i][j];
2204 }
2205 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002206 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002207 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002208 View child = mShortcutsAndWidgets.getChildAt(i);
2209 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2210 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002211 // We do a null check here because the item info can be null in the case of the
2212 // AllApps button in the hotseat.
2213 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002214 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2215 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2216 info.requiresDbUpdate = true;
2217 }
Adam Cohen2acce882012-03-28 19:03:19 -07002218 info.cellX = lp.cellX = lp.tmpCellX;
2219 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002220 info.spanX = lp.cellHSpan;
2221 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002222 }
Adam Cohen482ed822012-03-02 14:15:13 -08002223 }
Adam Cohen2acce882012-03-28 19:03:19 -07002224 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002225 }
2226
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002227 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002228 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002229 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002230 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002231 lp.useTmpCoords = useTempCoords;
2232 }
2233 }
2234
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002235 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002236 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2237 int[] result = new int[2];
2238 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002239 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002240 resultSpan);
2241 if (result[0] >= 0 && result[1] >= 0) {
2242 copyCurrentStateToSolution(solution, false);
2243 solution.dragViewX = result[0];
2244 solution.dragViewY = result[1];
2245 solution.dragViewSpanX = resultSpan[0];
2246 solution.dragViewSpanY = resultSpan[1];
2247 solution.isSolution = true;
2248 } else {
2249 solution.isSolution = false;
2250 }
2251 return solution;
2252 }
2253
2254 public void prepareChildForDrag(View child) {
2255 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002256 }
2257
Adam Cohen19f37922012-03-21 11:59:11 -07002258 /* This seems like it should be obvious and straight-forward, but when the direction vector
2259 needs to match with the notion of the dragView pushing other views, we have to employ
2260 a slightly more subtle notion of the direction vector. The question is what two points is
2261 the vector between? The center of the dragView and its desired destination? Not quite, as
2262 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2263 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2264 or right, which helps make pushing feel right.
2265 */
2266 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2267 int spanY, View dragView, int[] resultDirection) {
2268 int[] targetDestination = new int[2];
2269
2270 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2271 Rect dragRect = new Rect();
2272 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2273 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2274
2275 Rect dropRegionRect = new Rect();
2276 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2277 dragView, dropRegionRect, mIntersectingViews);
2278
2279 int dropRegionSpanX = dropRegionRect.width();
2280 int dropRegionSpanY = dropRegionRect.height();
2281
2282 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2283 dropRegionRect.height(), dropRegionRect);
2284
2285 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2286 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2287
2288 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2289 deltaX = 0;
2290 }
2291 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2292 deltaY = 0;
2293 }
2294
2295 if (deltaX == 0 && deltaY == 0) {
2296 // No idea what to do, give a random direction.
2297 resultDirection[0] = 1;
2298 resultDirection[1] = 0;
2299 } else {
2300 computeDirectionVector(deltaX, deltaY, resultDirection);
2301 }
2302 }
2303
2304 // For a given cell and span, fetch the set of views intersecting the region.
2305 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2306 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2307 if (boundingRect != null) {
2308 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2309 }
2310 intersectingViews.clear();
2311 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2312 Rect r1 = new Rect();
2313 final int count = mShortcutsAndWidgets.getChildCount();
2314 for (int i = 0; i < count; i++) {
2315 View child = mShortcutsAndWidgets.getChildAt(i);
2316 if (child == dragView) continue;
2317 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2318 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2319 if (Rect.intersects(r0, r1)) {
2320 mIntersectingViews.add(child);
2321 if (boundingRect != null) {
2322 boundingRect.union(r1);
2323 }
2324 }
2325 }
2326 }
2327
2328 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2329 View dragView, int[] result) {
2330 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2331 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2332 mIntersectingViews);
2333 return !mIntersectingViews.isEmpty();
2334 }
2335
2336 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002337 completeAndClearReorderPreviewAnimations();
2338 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2339 final int count = mShortcutsAndWidgets.getChildCount();
2340 for (int i = 0; i < count; i++) {
2341 View child = mShortcutsAndWidgets.getChildAt(i);
2342 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2343 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2344 lp.tmpCellX = lp.cellX;
2345 lp.tmpCellY = lp.cellY;
2346 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2347 0, false, false);
2348 }
Adam Cohen19f37922012-03-21 11:59:11 -07002349 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002350 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002351 }
Adam Cohen19f37922012-03-21 11:59:11 -07002352 }
2353
Adam Cohenbebf0422012-04-11 18:06:28 -07002354 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2355 View dragView, int[] direction, boolean commit) {
2356 int[] pixelXY = new int[2];
2357 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2358
2359 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002360 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002361 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2362
2363 setUseTempCoords(true);
2364 if (swapSolution != null && swapSolution.isSolution) {
2365 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2366 // committing anything or animating anything as we just want to determine if a solution
2367 // exists
2368 copySolutionToTempState(swapSolution, dragView);
2369 setItemPlacementDirty(true);
2370 animateItemsToSolution(swapSolution, dragView, commit);
2371
2372 if (commit) {
2373 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002374 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002375 setItemPlacementDirty(false);
2376 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002377 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2378 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002379 }
2380 mShortcutsAndWidgets.requestLayout();
2381 }
2382 return swapSolution.isSolution;
2383 }
2384
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002385 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002386 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002387 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002388 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002389
2390 if (resultSpan == null) {
2391 resultSpan = new int[2];
2392 }
2393
Adam Cohen19f37922012-03-21 11:59:11 -07002394 // When we are checking drop validity or actually dropping, we don't recompute the
2395 // direction vector, since we want the solution to match the preview, and it's possible
2396 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002397 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2398 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002399 mDirectionVector[0] = mPreviousReorderDirection[0];
2400 mDirectionVector[1] = mPreviousReorderDirection[1];
2401 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002402 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2403 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2404 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002405 }
2406 } else {
2407 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2408 mPreviousReorderDirection[0] = mDirectionVector[0];
2409 mPreviousReorderDirection[1] = mDirectionVector[1];
2410 }
2411
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002412 // Find a solution involving pushing / displacing any items in the way
2413 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002414 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2415
2416 // We attempt the approach which doesn't shuffle views at all
2417 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2418 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2419
2420 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002421
2422 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2423 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002424 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2425 finalSolution = swapSolution;
2426 } else if (noShuffleSolution.isSolution) {
2427 finalSolution = noShuffleSolution;
2428 }
2429
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002430 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002431 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002432 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2433 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002434 result[0] = finalSolution.dragViewX;
2435 result[1] = finalSolution.dragViewY;
2436 resultSpan[0] = finalSolution.dragViewSpanX;
2437 resultSpan[1] = finalSolution.dragViewSpanY;
2438 } else {
2439 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2440 }
2441 return result;
2442 }
2443
Adam Cohen482ed822012-03-02 14:15:13 -08002444 boolean foundSolution = true;
2445 if (!DESTRUCTIVE_REORDER) {
2446 setUseTempCoords(true);
2447 }
2448
2449 if (finalSolution != null) {
2450 result[0] = finalSolution.dragViewX;
2451 result[1] = finalSolution.dragViewY;
2452 resultSpan[0] = finalSolution.dragViewSpanX;
2453 resultSpan[1] = finalSolution.dragViewSpanY;
2454
2455 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2456 // committing anything or animating anything as we just want to determine if a solution
2457 // exists
2458 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2459 if (!DESTRUCTIVE_REORDER) {
2460 copySolutionToTempState(finalSolution, dragView);
2461 }
2462 setItemPlacementDirty(true);
2463 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2464
Adam Cohen19f37922012-03-21 11:59:11 -07002465 if (!DESTRUCTIVE_REORDER &&
2466 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002467 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002468 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002469 setItemPlacementDirty(false);
2470 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002471 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2472 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002473 }
2474 }
2475 } else {
2476 foundSolution = false;
2477 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2478 }
2479
2480 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2481 setUseTempCoords(false);
2482 }
Adam Cohen482ed822012-03-02 14:15:13 -08002483
Michael Jurkaa52570f2012-03-20 03:18:20 -07002484 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002485 return result;
2486 }
2487
Adam Cohen19f37922012-03-21 11:59:11 -07002488 void setItemPlacementDirty(boolean dirty) {
2489 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002490 }
Adam Cohen19f37922012-03-21 11:59:11 -07002491 boolean isItemPlacementDirty() {
2492 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002493 }
2494
Adam Cohen091440a2015-03-18 14:16:05 -07002495 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002496 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002497 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2498 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002499 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002500 boolean isSolution = false;
2501 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2502
Adam Cohenf3900c22012-11-16 18:28:11 -08002503 void save() {
2504 // Copy current state into savedMap
2505 for (View v: map.keySet()) {
2506 map.get(v).copy(savedMap.get(v));
2507 }
2508 }
2509
2510 void restore() {
2511 // Restore current state from savedMap
2512 for (View v: savedMap.keySet()) {
2513 savedMap.get(v).copy(map.get(v));
2514 }
2515 }
2516
2517 void add(View v, CellAndSpan cs) {
2518 map.put(v, cs);
2519 savedMap.put(v, new CellAndSpan());
2520 sortedViews.add(v);
2521 }
2522
Adam Cohen482ed822012-03-02 14:15:13 -08002523 int area() {
2524 return dragViewSpanX * dragViewSpanY;
2525 }
Adam Cohen8baab352012-03-20 17:39:21 -07002526 }
2527
2528 private class CellAndSpan {
2529 int x, y;
2530 int spanX, spanY;
2531
Adam Cohenf3900c22012-11-16 18:28:11 -08002532 public CellAndSpan() {
2533 }
2534
2535 public void copy(CellAndSpan copy) {
2536 copy.x = x;
2537 copy.y = y;
2538 copy.spanX = spanX;
2539 copy.spanY = spanY;
2540 }
2541
Adam Cohen8baab352012-03-20 17:39:21 -07002542 public CellAndSpan(int x, int y, int spanX, int spanY) {
2543 this.x = x;
2544 this.y = y;
2545 this.spanX = spanX;
2546 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002547 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002548
2549 public String toString() {
2550 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2551 }
2552
Adam Cohen482ed822012-03-02 14:15:13 -08002553 }
2554
Adam Cohendf035382011-04-11 17:22:04 -07002555 /**
Adam Cohendf035382011-04-11 17:22:04 -07002556 * Find a starting cell position that will fit the given bounds nearest the requested
2557 * cell location. Uses Euclidean distance to score multiple vacant areas.
2558 *
2559 * @param pixelX The X location at which you want to search for a vacant area.
2560 * @param pixelY The Y location at which you want to search for a vacant area.
2561 * @param spanX Horizontal span of the object.
2562 * @param spanY Vertical span of the object.
2563 * @param ignoreView Considers space occupied by this view as unoccupied
2564 * @param result Previously returned value to possibly recycle.
2565 * @return The X, Y cell of a vacant area that can contain this object,
2566 * nearest the requested location.
2567 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002568 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2569 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002570 }
2571
Michael Jurka0280c3b2010-09-17 15:00:07 -07002572 boolean existsEmptyCell() {
2573 return findCellForSpan(null, 1, 1);
2574 }
2575
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002576 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002577 * Finds the upper-left coordinate of the first rectangle in the grid that can
2578 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2579 * then this method will only return coordinates for rectangles that contain the cell
2580 * (intersectX, intersectY)
2581 *
2582 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2583 * can be found.
2584 * @param spanX The horizontal span of the cell we want to find.
2585 * @param spanY The vertical span of the cell we want to find.
2586 *
2587 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002588 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002589 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002590 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002591 final int endX = mCountX - (spanX - 1);
2592 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002593
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002594 for (int y = 0; y < endY && !foundCell; y++) {
2595 inner:
2596 for (int x = 0; x < endX; x++) {
2597 for (int i = 0; i < spanX; i++) {
2598 for (int j = 0; j < spanY; j++) {
2599 if (mOccupied[x + i][y + j]) {
2600 // small optimization: we can skip to after the column we just found
2601 // an occupied cell
2602 x += i;
2603 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002604 }
2605 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002606 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002607 if (cellXY != null) {
2608 cellXY[0] = x;
2609 cellXY[1] = y;
2610 }
2611 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002612 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002613 }
2614 }
2615
Michael Jurka28750fb2010-09-24 17:43:49 -07002616 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002617 }
2618
2619 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002620 * A drag event has begun over this layout.
2621 * It may have begun over this layout (in which case onDragChild is called first),
2622 * or it may have begun on another layout.
2623 */
2624 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002625 mDragging = true;
2626 }
2627
2628 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002629 * Called when drag has left this CellLayout or has been completed (successfully or not)
2630 */
2631 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002632 // This can actually be called when we aren't in a drag, e.g. when adding a new
2633 // item to this layout via the customize drawer.
2634 // Guard against that case.
2635 if (mDragging) {
2636 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002637 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002638
2639 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002640 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002641 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2642 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002643 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002644 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002645 }
2646
2647 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002648 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002649 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002650 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002651 *
2652 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002653 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002654 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002655 if (child != null) {
2656 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002657 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002658 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002659 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002660 }
2661
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002662 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002663 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002664 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002665 * @param cellX X coordinate of upper left corner expressed as a cell position
2666 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002667 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002668 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002669 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002670 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002671 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002672 final int cellWidth = mCellWidth;
2673 final int cellHeight = mCellHeight;
2674 final int widthGap = mWidthGap;
2675 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002676
Winson Chung4b825dcd2011-06-19 12:41:22 -07002677 final int hStartPadding = getPaddingLeft();
2678 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002679
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002680 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2681 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2682
2683 int x = hStartPadding + cellX * (cellWidth + widthGap);
2684 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002685
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002686 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002687 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002688
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002689 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002690 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002691 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002692 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002693 * @param width Width in pixels
2694 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002695 * @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 -08002696 */
Adam Cohen2e6da152015-05-06 11:42:25 -07002697 public static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
Sunny Goyal3a30cfe2015-07-16 17:27:43 -07002698 return rectToCell(launcher.getDeviceProfile(), launcher, width, height, result);
2699 }
2700
2701 public static int[] rectToCell(DeviceProfile grid, Context context, int width, int height,
2702 int[] result) {
2703 Rect padding = grid.getWorkspacePadding(Utilities.isRtl(context.getResources()));
Winson Chung5f8afe62013-08-12 16:19:28 -07002704
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002705 // Always assume we're working with the smallest span to make sure we
2706 // reserve enough space in both orientations.
Sunny Goyalc6205602015-05-21 20:46:33 -07002707 int parentWidth = DeviceProfile.calculateCellWidth(grid.widthPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002708 - padding.left - padding.right, (int) grid.inv.numColumns);
Sunny Goyalc6205602015-05-21 20:46:33 -07002709 int parentHeight = DeviceProfile.calculateCellHeight(grid.heightPx
Adam Cohen2e6da152015-05-06 11:42:25 -07002710 - padding.top - padding.bottom, (int) grid.inv.numRows);
Winson Chung66700732013-08-20 16:56:15 -07002711 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002712
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002713 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002714 int spanX = (int) Math.ceil(width / (float) smallerSize);
2715 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002716
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002717 if (result == null) {
2718 return new int[] { spanX, spanY };
2719 }
2720 result[0] = spanX;
2721 result[1] = spanY;
2722 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002723 }
2724
2725 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002726 * Calculate the grid spans needed to fit given item
2727 */
2728 public void calculateSpans(ItemInfo info) {
2729 final int minWidth;
2730 final int minHeight;
2731
2732 if (info instanceof LauncherAppWidgetInfo) {
2733 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2734 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2735 } else if (info instanceof PendingAddWidgetInfo) {
2736 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2737 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2738 } else {
2739 // It's not a widget, so it must be 1x1
2740 info.spanX = info.spanY = 1;
2741 return;
2742 }
Adam Cohen2e6da152015-05-06 11:42:25 -07002743 int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
Patrick Dubroy047379a2010-12-19 22:02:04 -08002744 info.spanX = spans[0];
2745 info.spanY = spans[1];
2746 }
2747
Michael Jurka0280c3b2010-09-17 15:00:07 -07002748 private void clearOccupiedCells() {
2749 for (int x = 0; x < mCountX; x++) {
2750 for (int y = 0; y < mCountY; y++) {
2751 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002752 }
2753 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002754 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002755
Adam Cohend4844c32011-02-18 19:25:06 -08002756 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002757 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002758 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002759 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002760 }
2761
Adam Cohend4844c32011-02-18 19:25:06 -08002762 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002763 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002764 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002765 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002766 }
2767
Adam Cohen482ed822012-03-02 14:15:13 -08002768 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2769 boolean value) {
2770 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002771 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2772 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002773 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002774 }
2775 }
2776 }
2777
Adam Cohen2801caf2011-05-13 20:57:39 -07002778 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002779 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002780 (Math.max((mCountX - 1), 0) * mWidthGap);
2781 }
2782
2783 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002784 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002785 (Math.max((mCountY - 1), 0) * mHeightGap);
2786 }
2787
Michael Jurka66d72172011-04-12 16:29:25 -07002788 public boolean isOccupied(int x, int y) {
2789 if (x < mCountX && y < mCountY) {
2790 return mOccupied[x][y];
2791 } else {
2792 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2793 }
2794 }
2795
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002796 @Override
2797 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2798 return new CellLayout.LayoutParams(getContext(), attrs);
2799 }
2800
2801 @Override
2802 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2803 return p instanceof CellLayout.LayoutParams;
2804 }
2805
2806 @Override
2807 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2808 return new CellLayout.LayoutParams(p);
2809 }
2810
2811 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2812 /**
2813 * Horizontal location of the item in the grid.
2814 */
2815 @ViewDebug.ExportedProperty
2816 public int cellX;
2817
2818 /**
2819 * Vertical location of the item in the grid.
2820 */
2821 @ViewDebug.ExportedProperty
2822 public int cellY;
2823
2824 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002825 * Temporary horizontal location of the item in the grid during reorder
2826 */
2827 public int tmpCellX;
2828
2829 /**
2830 * Temporary vertical location of the item in the grid during reorder
2831 */
2832 public int tmpCellY;
2833
2834 /**
2835 * Indicates that the temporary coordinates should be used to layout the items
2836 */
2837 public boolean useTmpCoords;
2838
2839 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002840 * Number of cells spanned horizontally by the item.
2841 */
2842 @ViewDebug.ExportedProperty
2843 public int cellHSpan;
2844
2845 /**
2846 * Number of cells spanned vertically by the item.
2847 */
2848 @ViewDebug.ExportedProperty
2849 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002850
Adam Cohen1b607ed2011-03-03 17:26:50 -08002851 /**
2852 * Indicates whether the item will set its x, y, width and height parameters freely,
2853 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2854 */
Adam Cohend4844c32011-02-18 19:25:06 -08002855 public boolean isLockedToGrid = true;
2856
Adam Cohen482ed822012-03-02 14:15:13 -08002857 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002858 * Indicates that this item should use the full extents of its parent.
2859 */
2860 public boolean isFullscreen = false;
2861
2862 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002863 * Indicates whether this item can be reordered. Always true except in the case of the
2864 * the AllApps button.
2865 */
2866 public boolean canReorder = true;
2867
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002868 // X coordinate of the view in the layout.
2869 @ViewDebug.ExportedProperty
2870 int x;
2871 // Y coordinate of the view in the layout.
2872 @ViewDebug.ExportedProperty
2873 int y;
2874
Romain Guy84f296c2009-11-04 15:00:44 -08002875 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002876
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002877 public LayoutParams(Context c, AttributeSet attrs) {
2878 super(c, attrs);
2879 cellHSpan = 1;
2880 cellVSpan = 1;
2881 }
2882
2883 public LayoutParams(ViewGroup.LayoutParams source) {
2884 super(source);
2885 cellHSpan = 1;
2886 cellVSpan = 1;
2887 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002888
2889 public LayoutParams(LayoutParams source) {
2890 super(source);
2891 this.cellX = source.cellX;
2892 this.cellY = source.cellY;
2893 this.cellHSpan = source.cellHSpan;
2894 this.cellVSpan = source.cellVSpan;
2895 }
2896
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002897 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002898 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002899 this.cellX = cellX;
2900 this.cellY = cellY;
2901 this.cellHSpan = cellHSpan;
2902 this.cellVSpan = cellVSpan;
2903 }
2904
Adam Cohen2374abf2013-04-16 14:56:57 -07002905 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2906 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002907 if (isLockedToGrid) {
2908 final int myCellHSpan = cellHSpan;
2909 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002910 int myCellX = useTmpCoords ? tmpCellX : cellX;
2911 int myCellY = useTmpCoords ? tmpCellY : cellY;
2912
2913 if (invertHorizontally) {
2914 myCellX = colCount - myCellX - cellHSpan;
2915 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002916
Adam Cohend4844c32011-02-18 19:25:06 -08002917 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2918 leftMargin - rightMargin;
2919 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2920 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002921 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2922 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002923 }
2924 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002925
Winson Chungaafa03c2010-06-11 17:34:16 -07002926 public String toString() {
2927 return "(" + this.cellX + ", " + this.cellY + ")";
2928 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002929
2930 public void setWidth(int width) {
2931 this.width = width;
2932 }
2933
2934 public int getWidth() {
2935 return width;
2936 }
2937
2938 public void setHeight(int height) {
2939 this.height = height;
2940 }
2941
2942 public int getHeight() {
2943 return height;
2944 }
2945
2946 public void setX(int x) {
2947 this.x = x;
2948 }
2949
2950 public int getX() {
2951 return x;
2952 }
2953
2954 public void setY(int y) {
2955 this.y = y;
2956 }
2957
2958 public int getY() {
2959 return y;
2960 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002961 }
2962
Michael Jurka0280c3b2010-09-17 15:00:07 -07002963 // This class stores info for two purposes:
2964 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2965 // its spanX, spanY, and the screen it is on
2966 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2967 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2968 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002969 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002970 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002971 int cellX = -1;
2972 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002973 int spanX;
2974 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002975 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002976 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002977
Sunny Goyal83a8f042015-05-19 12:52:12 -07002978 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002979 cell = v;
2980 cellX = info.cellX;
2981 cellY = info.cellY;
2982 spanX = info.spanX;
2983 spanY = info.spanY;
2984 screenId = info.screenId;
2985 container = info.container;
2986 }
2987
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002988 @Override
2989 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002990 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2991 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002992 }
2993 }
Michael Jurkad771c962011-08-09 15:00:48 -07002994
Sunny Goyala9116722015-04-29 13:55:58 -07002995 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2996 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2997 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002998
2999 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3000 int x2 = x + spanX - 1;
3001 int y2 = y + spanY - 1;
3002 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3003 return false;
3004 }
3005 for (int i = x; i <= x2; i++) {
3006 for (int j = y; j <= y2; j++) {
3007 if (mOccupied[i][j]) {
3008 return false;
3009 }
3010 }
3011 }
3012
3013 return true;
3014 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003015}