blob: 5539d9f8af65cbce352b6cfe3021f1e696947191 [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;
Joe Onorato4be866d2010-10-10 11:26:02 -070028import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070029import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080030import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070031import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070032import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080033import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080034import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070035import android.graphics.drawable.Drawable;
Sunny Goyal2805e632015-05-20 15:35:32 -070036import android.graphics.drawable.TransitionDrawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080037import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070038import android.os.Parcelable;
Tony Wickham489fc562015-09-02 14:45:39 -070039import android.os.PowerManager;
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;
Sunny Goyald1a0e8b2015-08-27 17:45:46 -070056import com.android.launcher3.util.ParcelableSparseArray;
Adam Cohen091440a2015-03-18 14:16:05 -070057import com.android.launcher3.util.Thunk;
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;
Sunny Goyald1a0e8b2015-08-27 17:45:46 -070088 private boolean mJailContent = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089
Patrick Dubroyde7658b2010-09-27 11:15:43 -070090 // These are temporary variables to prevent having to allocate a new object just to
91 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -070092 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -070093 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070094
The Android Open Source Project31dd5032009-03-03 19:32:27 -080095 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080096 boolean[][] mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080097
Michael Jurkadee05892010-07-27 10:01:56 -070098 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -070099 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -0700100
Adam Cohen69ce2e52011-07-03 19:25:21 -0700101 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700102 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700103
Michael Jurka5f1c5092010-09-03 14:15:02 -0700104 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700105
Sunny Goyal2805e632015-05-20 15:35:32 -0700106 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
107 private final TransitionDrawable mBackground;
108
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700109 // These values allow a fixed measurement to be set on the CellLayout.
110 private int mFixedWidth = -1;
111 private int mFixedHeight = -1;
112
Michael Jurka33945b22010-12-21 18:19:38 -0800113 // If we're actively dragging something over this screen, mIsDragOverlapping is true
114 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700115
Winson Chung150fbab2010-09-29 17:14:26 -0700116 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700117 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700118 @Thunk Rect[] mDragOutlines = new Rect[4];
119 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700120 private InterruptibleInOutAnimator[] mDragOutlineAnims =
121 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700122
123 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700124 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700125 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700126
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700127 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800128
Sunny Goyal316490e2015-06-02 09:38:28 -0700129 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
130 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700131
132 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700133
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700134 // When a drag operation is in progress, holds the nearest cell to the touch point
135 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800136
Joe Onorato4be866d2010-10-10 11:26:02 -0700137 private boolean mDragging = false;
138
Patrick Dubroyce34a972010-10-19 10:34:32 -0700139 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700140 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700141
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800142 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700143 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800144
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800145 public static final int MODE_SHOW_REORDER_HINT = 0;
146 public static final int MODE_DRAG_OVER = 1;
147 public static final int MODE_ON_DROP = 2;
148 public static final int MODE_ON_DROP_EXTERNAL = 3;
149 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700150 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800151 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
152
Adam Cohena897f392012-04-27 18:12:05 -0700153 static final int LANDSCAPE = 0;
154 static final int PORTRAIT = 1;
155
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800156 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700157 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700158 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700159
Adam Cohen482ed822012-03-02 14:15:13 -0800160 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
161 private Rect mOccupiedRect = new Rect();
162 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700163 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700164 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800165
Sunny Goyal2805e632015-05-20 15:35:32 -0700166 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700167
Michael Jurkaca993832012-06-29 15:17:04 -0700168 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700169
Adam Cohenc9735cf2015-01-23 16:11:55 -0800170 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700171 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800172 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800173
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800174 public CellLayout(Context context) {
175 this(context, null);
176 }
177
178 public CellLayout(Context context, AttributeSet attrs) {
179 this(context, attrs, 0);
180 }
181
182 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
183 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700184
185 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
186 // the user where a dragged item will land when dropped.
187 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800188 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700189 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700190
Adam Cohen2e6da152015-05-06 11:42:25 -0700191 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800192
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
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700206 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700207 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700208
Sunny Goyal2805e632015-05-20 15:35:32 -0700209 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
210 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700211 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800212
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800213 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700214 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700215
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700216 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700217 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700218 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700219 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800220 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700221 }
222
223 // When dragging things around the home screens, we show a green outline of
224 // where the item will land. The outlines gradually fade out, leaving a trail
225 // behind the drag path.
226 // Set up all the animations that are used to implement this fading.
227 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700228 final float fromAlphaValue = 0;
229 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700230
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700231 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700232
233 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700234 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100235 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700236 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700237 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700238 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700239 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700240 final Bitmap outline = (Bitmap)anim.getTag();
241
242 // If an animation is started and then stopped very quickly, we can still
243 // get spurious updates we've cleared the tag. Guard against this.
244 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700245 @SuppressWarnings("all") // suppress dead code warning
246 final boolean debug = false;
247 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700248 Object val = animation.getAnimatedValue();
249 Log.d(TAG, "anim " + thisIndex + " update: " + val +
250 ", isStopped " + anim.isStopped());
251 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700252 // Try to prevent it from continuing to run
253 animation.cancel();
254 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700255 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800256 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700257 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700258 }
259 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700260 // The animation holds a reference to the drag outline bitmap as long is it's
261 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700262 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700263 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700264 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700265 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700266 anim.setTag(null);
267 }
268 }
269 });
270 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700271 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700272
Michael Jurkaa52570f2012-03-20 03:18:20 -0700273 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700274 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700275 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700276
Mady Melloref044dd2015-06-02 15:35:07 -0700277 mStylusEventHelper = new StylusEventHelper(this);
278
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700279 mTouchFeedbackView = new ClickShadowView(context);
280 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700281 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700282 }
283
Adam Cohenc9735cf2015-01-23 16:11:55 -0800284 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700285 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800286 mUseTouchHelper = enable;
287 if (!enable) {
288 ViewCompat.setAccessibilityDelegate(this, null);
289 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
290 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
291 setOnClickListener(mLauncher);
292 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700293 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
294 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
295 mTouchHelper = new WorkspaceAccessibilityHelper(this);
296 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
297 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
298 mTouchHelper = new FolderAccessibilityHelper(this);
299 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800300 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
301 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
302 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
303 setOnClickListener(mTouchHelper);
304 }
305
306 // Invalidate the accessibility hierarchy
307 if (getParent() != null) {
308 getParent().notifySubtreeAccessibilityStateChanged(
309 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
310 }
311 }
312
313 @Override
314 public boolean dispatchHoverEvent(MotionEvent event) {
315 // Always attempt to dispatch hover events to accessibility first.
316 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
317 return true;
318 }
319 return super.dispatchHoverEvent(event);
320 }
321
322 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800323 public boolean onInterceptTouchEvent(MotionEvent ev) {
324 if (mUseTouchHelper ||
325 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
326 return true;
327 }
328 return false;
329 }
330
Mady Melloref044dd2015-06-02 15:35:07 -0700331 @Override
332 public boolean onTouchEvent(MotionEvent ev) {
333 boolean handled = super.onTouchEvent(ev);
334 // Stylus button press on a home screen should not switch between overview mode and
335 // the home screen mode, however, once in overview mode stylus button press should be
336 // enabled to allow rearranging the different home screens. So check what mode
337 // the workspace is in, and only perform stylus button presses while in overview mode.
338 if (mLauncher.mWorkspace.isInOverviewMode()
339 && mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
340 return true;
341 }
342 return handled;
343 }
344
Chris Craik01f2d7f2013-10-01 14:41:56 -0700345 public void enableHardwareLayer(boolean hasLayer) {
346 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700347 }
348
349 public void buildHardwareLayer() {
350 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700351 }
352
Adam Cohen307fe232012-08-16 17:55:58 -0700353 public float getChildrenScale() {
354 return mIsHotseat ? mHotseatScale : 1.0f;
355 }
356
Winson Chung5f8afe62013-08-12 16:19:28 -0700357 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700358 mFixedCellWidth = mCellWidth = width;
359 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700360 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
361 mCountX, mCountY);
362 }
363
Adam Cohen2801caf2011-05-13 20:57:39 -0700364 public void setGridSize(int x, int y) {
365 mCountX = x;
366 mCountY = y;
367 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800368 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700369 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700370 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700371 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700372 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700373 }
374
Adam Cohen2374abf2013-04-16 14:56:57 -0700375 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
376 public void setInvertIfRtl(boolean invert) {
377 mShortcutsAndWidgets.setInvertIfRtl(invert);
378 }
379
Adam Cohen917e3882013-10-31 15:03:35 -0700380 public void setDropPending(boolean pending) {
381 mDropPending = pending;
382 }
383
384 public boolean isDropPending() {
385 return mDropPending;
386 }
387
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700388 @Override
389 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700390 if (icon == null || background == null) {
391 mTouchFeedbackView.setBitmap(null);
392 mTouchFeedbackView.animate().cancel();
393 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700394 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700395 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
396 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700397 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800398 }
399 }
400
Adam Cohenc50438c2014-08-19 17:43:05 -0700401 void disableDragTarget() {
402 mIsDragTarget = false;
403 }
404
405 boolean isDragTarget() {
406 return mIsDragTarget;
407 }
408
409 void setIsDragOverlapping(boolean isDragOverlapping) {
410 if (mIsDragOverlapping != isDragOverlapping) {
411 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700412 if (mIsDragOverlapping) {
413 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
414 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700415 if (mBackgroundAlpha > 0f) {
416 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
417 } else {
418 mBackground.resetTransition();
419 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700420 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700421 invalidate();
422 }
423 }
424
Sunny Goyald1a0e8b2015-08-27 17:45:46 -0700425 public boolean getIsDragOverlapping() {
Michael Jurka33945b22010-12-21 18:19:38 -0800426 return mIsDragOverlapping;
427 }
428
Sunny Goyald1a0e8b2015-08-27 17:45:46 -0700429 public void disableJailContent() {
430 mJailContent = false;
431 }
432
433 @Override
434 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
435 if (mJailContent) {
436 ParcelableSparseArray jail = getJailedArray(container);
437 super.dispatchSaveInstanceState(jail);
438 container.put(R.id.cell_layout_jail_id, jail);
439 } else {
440 super.dispatchSaveInstanceState(container);
441 }
442 }
443
444 @Override
445 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
446 super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
447 }
448
449 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
450 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
451 return parcelable instanceof ParcelableSparseArray ?
452 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
453 }
454
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700455 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700456 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700457 if (!mIsDragTarget) {
458 return;
459 }
460
Michael Jurka3e7c7632010-10-02 16:01:03 -0700461 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
462 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
463 // When we're small, we are either drawn normally or in the "accepts drops" state (during
464 // a drag). However, we also drag the mini hover background *over* one of those two
465 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700466 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700467 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700468 }
Romain Guya6abce82009-11-10 02:54:41 -0800469
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700470 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700471 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700472 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700473 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800474 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700475 mTempRect.set(r);
476 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700477 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700478 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700479 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700480 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700481 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800482
Adam Cohen482ed822012-03-02 14:15:13 -0800483 if (DEBUG_VISUALIZE_OCCUPIED) {
484 int[] pt = new int[2];
485 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700486 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800487 for (int i = 0; i < mCountX; i++) {
488 for (int j = 0; j < mCountY; j++) {
489 if (mOccupied[i][j]) {
490 cellToPoint(i, j, pt);
491 canvas.save();
492 canvas.translate(pt[0], pt[1]);
493 cd.draw(canvas);
494 canvas.restore();
495 }
496 }
497 }
498 }
499
Andrew Flynn850d2e72012-04-26 16:51:20 -0700500 int previewOffset = FolderRingAnimator.sPreviewSize;
501
Adam Cohen69ce2e52011-07-03 19:25:21 -0700502 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700503 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700504 for (int i = 0; i < mFolderOuterRings.size(); i++) {
505 FolderRingAnimator fra = mFolderOuterRings.get(i);
506
Adam Cohen5108bc02013-09-20 17:04:51 -0700507 Drawable d;
508 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700509 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700510 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700511
Winson Chung89f97052013-09-20 11:32:26 -0700512 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700513 int centerX = mTempLocation[0] + mCellWidth / 2;
514 int centerY = mTempLocation[1] + previewOffset / 2 +
515 child.getPaddingTop() + grid.folderBackgroundOffset;
516
Adam Cohen5108bc02013-09-20 17:04:51 -0700517 // Draw outer ring, if it exists
518 if (FolderIcon.HAS_OUTER_RING) {
519 d = FolderRingAnimator.sSharedOuterRingDrawable;
520 width = (int) (fra.getOuterRingSize() * getChildrenScale());
521 height = width;
522 canvas.save();
523 canvas.translate(centerX - width / 2, centerY - height / 2);
524 d.setBounds(0, 0, width, height);
525 d.draw(canvas);
526 canvas.restore();
527 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700528
Winson Chung89f97052013-09-20 11:32:26 -0700529 // Draw inner ring
530 d = FolderRingAnimator.sSharedInnerRingDrawable;
531 width = (int) (fra.getInnerRingSize() * getChildrenScale());
532 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700533 canvas.save();
534 canvas.translate(centerX - width / 2, centerY - width / 2);
535 d.setBounds(0, 0, width, height);
536 d.draw(canvas);
537 canvas.restore();
538 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700539 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700540
541 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
542 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
543 int width = d.getIntrinsicWidth();
544 int height = d.getIntrinsicHeight();
545
546 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700547 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700548 if (child != null) {
549 int centerX = mTempLocation[0] + mCellWidth / 2;
550 int centerY = mTempLocation[1] + previewOffset / 2 +
551 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700552
Winson Chung89f97052013-09-20 11:32:26 -0700553 canvas.save();
554 canvas.translate(centerX - width / 2, centerY - width / 2);
555 d.setBounds(0, 0, width, height);
556 d.draw(canvas);
557 canvas.restore();
558 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700559 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700560 }
561
562 public void showFolderAccept(FolderRingAnimator fra) {
563 mFolderOuterRings.add(fra);
564 }
565
566 public void hideFolderAccept(FolderRingAnimator fra) {
567 if (mFolderOuterRings.contains(fra)) {
568 mFolderOuterRings.remove(fra);
569 }
570 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700571 }
572
Adam Cohenc51934b2011-07-26 21:07:43 -0700573 public void setFolderLeaveBehindCell(int x, int y) {
574 mFolderLeaveBehindCell[0] = x;
575 mFolderLeaveBehindCell[1] = y;
576 invalidate();
577 }
578
579 public void clearFolderLeaveBehind() {
580 mFolderLeaveBehindCell[0] = -1;
581 mFolderLeaveBehindCell[1] = -1;
582 invalidate();
583 }
584
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700585 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700586 public boolean shouldDelayChildPressedState() {
587 return false;
588 }
589
Adam Cohen1462de32012-07-24 22:34:36 -0700590 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700591 try {
592 dispatchRestoreInstanceState(states);
593 } catch (IllegalArgumentException ex) {
594 if (LauncherAppState.isDogfoodBuild()) {
595 throw ex;
596 }
597 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
598 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
599 }
Adam Cohen1462de32012-07-24 22:34:36 -0700600 }
601
Michael Jurkae6235dd2011-10-04 15:02:05 -0700602 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700603 public void cancelLongPress() {
604 super.cancelLongPress();
605
606 // Cancel long press for all children
607 final int count = getChildCount();
608 for (int i = 0; i < count; i++) {
609 final View child = getChildAt(i);
610 child.cancelLongPress();
611 }
612 }
613
Michael Jurkadee05892010-07-27 10:01:56 -0700614 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
615 mInterceptTouchListener = listener;
616 }
617
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800618 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700619 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800620 }
621
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800622 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700623 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800624 }
625
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800626 public void setIsHotseat(boolean isHotseat) {
627 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700628 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800629 }
630
Sunny Goyale9b651e2015-04-24 11:44:51 -0700631 public boolean isHotseat() {
632 return mIsHotseat;
633 }
634
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800635 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700636 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700637 final LayoutParams lp = params;
638
Andrew Flynnde38e422012-05-08 11:22:15 -0700639 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800640 if (child instanceof BubbleTextView) {
641 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700642 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800643 }
644
Adam Cohen307fe232012-08-16 17:55:58 -0700645 child.setScaleX(getChildrenScale());
646 child.setScaleY(getChildrenScale());
647
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800648 // Generate an id for each view, this assumes we have at most 256x256 cells
649 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700650 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700651 // If the horizontal or vertical span is set to -1, it is taken to
652 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700653 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
654 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800655
Winson Chungaafa03c2010-06-11 17:34:16 -0700656 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700657 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700658
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700659 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700660
Winson Chungaafa03c2010-06-11 17:34:16 -0700661 return true;
662 }
663 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800664 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700665
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800666 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700667 public void removeAllViews() {
668 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700669 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700670 }
671
672 @Override
673 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700674 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700675 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700676 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700677 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700678 }
679
680 @Override
681 public void removeView(View view) {
682 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700684 }
685
686 @Override
687 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700688 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
689 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700690 }
691
692 @Override
693 public void removeViewInLayout(View view) {
694 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700695 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700696 }
697
698 @Override
699 public void removeViews(int start, int count) {
700 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700701 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700702 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700703 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700704 }
705
706 @Override
707 public void removeViewsInLayout(int start, int count) {
708 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700709 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700710 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700711 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800712 }
713
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700714 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700715 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800716 * @param x X coordinate of the point
717 * @param y Y coordinate of the point
718 * @param result Array of 2 ints to hold the x and y coordinate of the cell
719 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700720 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700721 final int hStartPadding = getPaddingLeft();
722 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800723
724 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
725 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
726
Adam Cohend22015c2010-07-26 22:02:18 -0700727 final int xAxis = mCountX;
728 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800729
730 if (result[0] < 0) result[0] = 0;
731 if (result[0] >= xAxis) result[0] = xAxis - 1;
732 if (result[1] < 0) result[1] = 0;
733 if (result[1] >= yAxis) result[1] = yAxis - 1;
734 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700735
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800736 /**
737 * Given a point, return the cell that most closely encloses that point
738 * @param x X coordinate of the point
739 * @param y Y coordinate of the point
740 * @param result Array of 2 ints to hold the x and y coordinate of the cell
741 */
742 void pointToCellRounded(int x, int y, int[] result) {
743 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
744 }
745
746 /**
747 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700748 *
749 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800750 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700751 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800752 * @param result Array of 2 ints to hold the x and y coordinate of the point
753 */
754 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700755 final int hStartPadding = getPaddingLeft();
756 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800757
758 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
759 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
760 }
761
Adam Cohene3e27a82011-04-15 12:07:39 -0700762 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800763 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700764 *
765 * @param cellX X coordinate of the cell
766 * @param cellY Y coordinate of the cell
767 *
768 * @param result Array of 2 ints to hold the x and y coordinate of the point
769 */
770 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700771 regionToCenterPoint(cellX, cellY, 1, 1, result);
772 }
773
774 /**
775 * Given a cell coordinate and span return the point that represents the center of the regio
776 *
777 * @param cellX X coordinate of the cell
778 * @param cellY Y coordinate of the cell
779 *
780 * @param result Array of 2 ints to hold the x and y coordinate of the point
781 */
782 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700783 final int hStartPadding = getPaddingLeft();
784 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700785 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
786 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
787 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
788 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700789 }
790
Adam Cohen19f37922012-03-21 11:59:11 -0700791 /**
792 * Given a cell coordinate and span fills out a corresponding pixel rect
793 *
794 * @param cellX X coordinate of the cell
795 * @param cellY Y coordinate of the cell
796 * @param result Rect in which to write the result
797 */
798 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
799 final int hStartPadding = getPaddingLeft();
800 final int vStartPadding = getPaddingTop();
801 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
802 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
803 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
804 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
805 }
806
Adam Cohen482ed822012-03-02 14:15:13 -0800807 public float getDistanceFromCell(float x, float y, int[] cell) {
808 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700809 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800810 }
811
Romain Guy84f296c2009-11-04 15:00:44 -0800812 int getCellWidth() {
813 return mCellWidth;
814 }
815
816 int getCellHeight() {
817 return mCellHeight;
818 }
819
Adam Cohend4844c32011-02-18 19:25:06 -0800820 int getWidthGap() {
821 return mWidthGap;
822 }
823
824 int getHeightGap() {
825 return mHeightGap;
826 }
827
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700828 public void setFixedSize(int width, int height) {
829 mFixedWidth = width;
830 mFixedHeight = height;
831 }
832
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800833 @Override
834 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800835 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800836 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700837 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
838 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700839 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
840 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700841 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700842 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
843 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700844 if (cw != mCellWidth || ch != mCellHeight) {
845 mCellWidth = cw;
846 mCellHeight = ch;
847 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
848 mHeightGap, mCountX, mCountY);
849 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700850 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700851
Winson Chung2d75f122013-09-23 16:53:31 -0700852 int newWidth = childWidthSize;
853 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700854 if (mFixedWidth > 0 && mFixedHeight > 0) {
855 newWidth = mFixedWidth;
856 newHeight = mFixedHeight;
857 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800858 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
859 }
860
Adam Cohend22015c2010-07-26 22:02:18 -0700861 int numWidthGaps = mCountX - 1;
862 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800863
Adam Cohen234c4cd2011-07-17 21:03:04 -0700864 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700865 int hSpace = childWidthSize;
866 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700867 int hFreeSpace = hSpace - (mCountX * mCellWidth);
868 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700869 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
870 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700871 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
872 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700873 } else {
874 mWidthGap = mOriginalWidthGap;
875 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700876 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700877
878 // Make the feedback view large enough to hold the blur bitmap.
879 mTouchFeedbackView.measure(
880 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
881 MeasureSpec.EXACTLY),
882 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
883 MeasureSpec.EXACTLY));
884
885 mShortcutsAndWidgets.measure(
886 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
887 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
888
889 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
890 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700891 if (mFixedWidth > 0 && mFixedHeight > 0) {
892 setMeasuredDimension(maxWidth, maxHeight);
893 } else {
894 setMeasuredDimension(widthSize, heightSize);
895 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800896 }
897
898 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700899 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700900 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
901 (mCountX * mCellWidth);
902 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
903 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700904
905 mTouchFeedbackView.layout(left, top,
906 left + mTouchFeedbackView.getMeasuredWidth(),
907 top + mTouchFeedbackView.getMeasuredHeight());
908 mShortcutsAndWidgets.layout(left, top,
909 left + r - l,
910 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800911 }
912
913 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700914 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
915 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700916
917 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700918 mBackground.getPadding(mTempRect);
919 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
920 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700921 }
922
923 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800924 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700925 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800926 }
927
928 @Override
929 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700930 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800931 }
932
Michael Jurka5f1c5092010-09-03 14:15:02 -0700933 public float getBackgroundAlpha() {
934 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700935 }
936
Michael Jurka5f1c5092010-09-03 14:15:02 -0700937 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800938 if (mBackgroundAlpha != alpha) {
939 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700940 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800941 }
Michael Jurkadee05892010-07-27 10:01:56 -0700942 }
943
Sunny Goyal2805e632015-05-20 15:35:32 -0700944 @Override
945 protected boolean verifyDrawable(Drawable who) {
946 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
947 }
948
Michael Jurkaa52570f2012-03-20 03:18:20 -0700949 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700950 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700951 }
952
Michael Jurkaa52570f2012-03-20 03:18:20 -0700953 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700954 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700955 }
956
Patrick Dubroy440c3602010-07-13 17:50:32 -0700957 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700958 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700959 }
960
Adam Cohen76fc0852011-06-17 13:26:23 -0700961 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800962 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700963 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800964 boolean[][] occupied = mOccupied;
965 if (!permanent) {
966 occupied = mTmpOccupied;
967 }
968
Adam Cohen19f37922012-03-21 11:59:11 -0700969 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700970 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
971 final ItemInfo info = (ItemInfo) child.getTag();
972
973 // We cancel any existing animations
974 if (mReorderAnimators.containsKey(lp)) {
975 mReorderAnimators.get(lp).cancel();
976 mReorderAnimators.remove(lp);
977 }
978
Adam Cohen482ed822012-03-02 14:15:13 -0800979 final int oldX = lp.x;
980 final int oldY = lp.y;
981 if (adjustOccupied) {
982 occupied[lp.cellX][lp.cellY] = false;
983 occupied[cellX][cellY] = true;
984 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700985 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800986 if (permanent) {
987 lp.cellX = info.cellX = cellX;
988 lp.cellY = info.cellY = cellY;
989 } else {
990 lp.tmpCellX = cellX;
991 lp.tmpCellY = cellY;
992 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700993 clc.setupLp(lp);
994 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800995 final int newX = lp.x;
996 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700997
Adam Cohen76fc0852011-06-17 13:26:23 -0700998 lp.x = oldX;
999 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001000
Adam Cohen482ed822012-03-02 14:15:13 -08001001 // Exit early if we're not actually moving the view
1002 if (oldX == newX && oldY == newY) {
1003 lp.isLockedToGrid = true;
1004 return true;
1005 }
1006
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001007 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001008 va.setDuration(duration);
1009 mReorderAnimators.put(lp, va);
1010
1011 va.addUpdateListener(new AnimatorUpdateListener() {
1012 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001013 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001014 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001015 lp.x = (int) ((1 - r) * oldX + r * newX);
1016 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001017 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001018 }
1019 });
Adam Cohen482ed822012-03-02 14:15:13 -08001020 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001021 boolean cancelled = false;
1022 public void onAnimationEnd(Animator animation) {
1023 // If the animation was cancelled, it means that another animation
1024 // has interrupted this one, and we don't want to lock the item into
1025 // place just yet.
1026 if (!cancelled) {
1027 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001028 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001029 }
1030 if (mReorderAnimators.containsKey(lp)) {
1031 mReorderAnimators.remove(lp);
1032 }
1033 }
1034 public void onAnimationCancel(Animator animation) {
1035 cancelled = true;
1036 }
1037 });
Adam Cohen482ed822012-03-02 14:15:13 -08001038 va.setStartDelay(delay);
1039 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001040 return true;
1041 }
1042 return false;
1043 }
1044
Adam Cohen482ed822012-03-02 14:15:13 -08001045 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
Sunny Goyale78e3d72015-09-24 11:23:31 -07001046 int cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001047 final int oldDragCellX = mDragCell[0];
1048 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001049
Adam Cohen2801caf2011-05-13 20:57:39 -07001050 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001051 return;
1052 }
1053
Adam Cohen482ed822012-03-02 14:15:13 -08001054 if (cellX != oldDragCellX || cellY != oldDragCellY) {
Sunny Goyale78e3d72015-09-24 11:23:31 -07001055 Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
1056 Rect dragRegion = dragObject.dragView.getDragRegion();
1057
Adam Cohen482ed822012-03-02 14:15:13 -08001058 mDragCell[0] = cellX;
1059 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001060 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001061 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001062 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001063
Joe Onorato4be866d2010-10-10 11:26:02 -07001064 int left = topLeft[0];
1065 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001066
Winson Chungb8c69f32011-10-19 21:36:08 -07001067 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001068 // When drawing the drag outline, it did not account for margin offsets
1069 // added by the view's parent.
1070 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1071 left += lp.leftMargin;
1072 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001073
Adam Cohen99e8b402011-03-25 19:23:43 -07001074 // Offsets due to the size difference between the View and the dragOutline.
1075 // There is a size difference to account for the outer blur, which may lie
1076 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001077 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001078 // We center about the x axis
1079 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1080 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001081 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001082 if (dragOffset != null && dragRegion != null) {
1083 // Center the drag region *horizontally* in the cell and apply a drag
1084 // outline offset
1085 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1086 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001087 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1088 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1089 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001090 } else {
1091 // Center the drag outline in the cell
1092 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1093 - dragOutline.getWidth()) / 2;
1094 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1095 - dragOutline.getHeight()) / 2;
1096 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001097 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001098 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001099 mDragOutlineAnims[oldIndex].animateOut();
1100 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001101 Rect r = mDragOutlines[mDragOutlineCurrent];
1102 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1103 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001104 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001105 }
Winson Chung150fbab2010-09-29 17:14:26 -07001106
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001107 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1108 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001109
1110 if (dragObject.stateAnnouncer != null) {
1111 String msg;
1112 if (isHotseat()) {
1113 msg = getContext().getString(R.string.move_to_hotseat_position,
1114 Math.max(cellX, cellY) + 1);
1115 } else {
1116 msg = getContext().getString(R.string.move_to_empty_cell,
1117 cellY + 1, cellX + 1);
1118 }
1119 dragObject.stateAnnouncer.announce(msg);
1120 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001121 }
1122 }
1123
Adam Cohene0310962011-04-18 16:15:31 -07001124 public void clearDragOutlines() {
1125 final int oldIndex = mDragOutlineCurrent;
1126 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001127 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001128 }
1129
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001130 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001131 * Find a vacant area that will fit the given bounds nearest the requested
1132 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001133 *
Romain Guy51afc022009-05-04 18:03:43 -07001134 * @param pixelX The X location at which you want to search for a vacant area.
1135 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001136 * @param spanX Horizontal span of the object.
1137 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001138 * @param result Array in which to place the result, or null (in which case a new array will
1139 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001140 * @return The X, Y cell of a vacant area that can contain this object,
1141 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001142 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001143 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1144 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001145 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001146
Michael Jurka6a1435d2010-09-27 17:35:12 -07001147 /**
1148 * Find a vacant area that will fit the given bounds nearest the requested
1149 * cell location. Uses Euclidean distance to score multiple vacant areas.
1150 *
1151 * @param pixelX The X location at which you want to search for a vacant area.
1152 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001153 * @param minSpanX The minimum horizontal span required
1154 * @param minSpanY The minimum vertical span required
1155 * @param spanX Horizontal span of the object.
1156 * @param spanY Vertical span of the object.
1157 * @param result Array in which to place the result, or null (in which case a new array will
1158 * be allocated)
1159 * @return The X, Y cell of a vacant area that can contain this object,
1160 * nearest the requested location.
1161 */
1162 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1163 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001164 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001165 result, resultSpan);
1166 }
1167
Adam Cohend41fbf52012-02-16 23:53:59 -08001168 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1169 private void lazyInitTempRectStack() {
1170 if (mTempRectStack.isEmpty()) {
1171 for (int i = 0; i < mCountX * mCountY; i++) {
1172 mTempRectStack.push(new Rect());
1173 }
1174 }
1175 }
Adam Cohen482ed822012-03-02 14:15:13 -08001176
Adam Cohend41fbf52012-02-16 23:53:59 -08001177 private void recycleTempRects(Stack<Rect> used) {
1178 while (!used.isEmpty()) {
1179 mTempRectStack.push(used.pop());
1180 }
1181 }
1182
1183 /**
1184 * Find a vacant area that will fit the given bounds nearest the requested
1185 * cell location. Uses Euclidean distance to score multiple vacant areas.
1186 *
1187 * @param pixelX The X location at which you want to search for a vacant area.
1188 * @param pixelY The Y location at which you want to search for a vacant area.
1189 * @param minSpanX The minimum horizontal span required
1190 * @param minSpanY The minimum vertical span required
1191 * @param spanX Horizontal span of the object.
1192 * @param spanY Vertical span of the object.
1193 * @param ignoreOccupied If true, the result can be an occupied cell
1194 * @param result Array in which to place the result, or null (in which case a new array will
1195 * be allocated)
1196 * @return The X, Y cell of a vacant area that can contain this object,
1197 * nearest the requested location.
1198 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001199 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1200 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001201 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001202
Adam Cohene3e27a82011-04-15 12:07:39 -07001203 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1204 // to the center of the item, but we are searching based on the top-left cell, so
1205 // we translate the point over to correspond to the top-left.
1206 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1207 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1208
Jeff Sharkey70864282009-04-07 21:08:40 -07001209 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001210 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001211 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001212 final Rect bestRect = new Rect(-1, -1, -1, -1);
1213 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001214
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001215 final int countX = mCountX;
1216 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001217
Adam Cohend41fbf52012-02-16 23:53:59 -08001218 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1219 spanX < minSpanX || spanY < minSpanY) {
1220 return bestXY;
1221 }
1222
1223 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001224 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001225 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1226 int ySize = -1;
1227 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001228 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001229 // First, let's see if this thing fits anywhere
1230 for (int i = 0; i < minSpanX; i++) {
1231 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001232 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001233 continue inner;
1234 }
Michael Jurkac28de512010-08-13 11:27:44 -07001235 }
1236 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001237 xSize = minSpanX;
1238 ySize = minSpanY;
1239
1240 // We know that the item will fit at _some_ acceptable size, now let's see
1241 // how big we can make it. We'll alternate between incrementing x and y spans
1242 // until we hit a limit.
1243 boolean incX = true;
1244 boolean hitMaxX = xSize >= spanX;
1245 boolean hitMaxY = ySize >= spanY;
1246 while (!(hitMaxX && hitMaxY)) {
1247 if (incX && !hitMaxX) {
1248 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001249 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001250 // We can't move out horizontally
1251 hitMaxX = true;
1252 }
1253 }
1254 if (!hitMaxX) {
1255 xSize++;
1256 }
1257 } else if (!hitMaxY) {
1258 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001259 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001260 // We can't move out vertically
1261 hitMaxY = true;
1262 }
1263 }
1264 if (!hitMaxY) {
1265 ySize++;
1266 }
1267 }
1268 hitMaxX |= xSize >= spanX;
1269 hitMaxY |= ySize >= spanY;
1270 incX = !incX;
1271 }
1272 incX = true;
1273 hitMaxX = xSize >= spanX;
1274 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001275 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001276 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001277 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001278
Adam Cohend41fbf52012-02-16 23:53:59 -08001279 // We verify that the current rect is not a sub-rect of any of our previous
1280 // candidates. In this case, the current rect is disqualified in favour of the
1281 // containing rect.
1282 Rect currentRect = mTempRectStack.pop();
1283 currentRect.set(x, y, x + xSize, y + ySize);
1284 boolean contained = false;
1285 for (Rect r : validRegions) {
1286 if (r.contains(currentRect)) {
1287 contained = true;
1288 break;
1289 }
1290 }
1291 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001292 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001293
Adam Cohend41fbf52012-02-16 23:53:59 -08001294 if ((distance <= bestDistance && !contained) ||
1295 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001296 bestDistance = distance;
1297 bestXY[0] = x;
1298 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001299 if (resultSpan != null) {
1300 resultSpan[0] = xSize;
1301 resultSpan[1] = ySize;
1302 }
1303 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001304 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001305 }
1306 }
1307
Adam Cohenc0dcf592011-06-01 15:30:43 -07001308 // Return -1, -1 if no suitable location found
1309 if (bestDistance == Double.MAX_VALUE) {
1310 bestXY[0] = -1;
1311 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001312 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001313 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001314 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001315 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001316
Adam Cohen482ed822012-03-02 14:15:13 -08001317 /**
1318 * Find a vacant area that will fit the given bounds nearest the requested
1319 * cell location, and will also weigh in a suggested direction vector of the
1320 * desired location. This method computers distance based on unit grid distances,
1321 * not pixel distances.
1322 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001323 * @param cellX The X cell nearest to which you want to search for a vacant area.
1324 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001325 * @param spanX Horizontal span of the object.
1326 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001327 * @param direction The favored direction in which the views should move from x, y
1328 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1329 * matches exactly. Otherwise we find the best matching direction.
1330 * @param occoupied The array which represents which cells in the CellLayout are occupied
1331 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001332 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001333 * @param result Array in which to place the result, or null (in which case a new array will
1334 * be allocated)
1335 * @return The X, Y cell of a vacant area that can contain this object,
1336 * nearest the requested location.
1337 */
1338 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001339 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001340 // Keep track of best-scoring drop area
1341 final int[] bestXY = result != null ? result : new int[2];
1342 float bestDistance = Float.MAX_VALUE;
1343 int bestDirectionScore = Integer.MIN_VALUE;
1344
1345 final int countX = mCountX;
1346 final int countY = mCountY;
1347
1348 for (int y = 0; y < countY - (spanY - 1); y++) {
1349 inner:
1350 for (int x = 0; x < countX - (spanX - 1); x++) {
1351 // First, let's see if this thing fits anywhere
1352 for (int i = 0; i < spanX; i++) {
1353 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001354 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001355 continue inner;
1356 }
1357 }
1358 }
1359
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001360 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001361 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001362 computeDirectionVector(x - cellX, y - cellY, curDirection);
1363 // The direction score is just the dot product of the two candidate direction
1364 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001365 int curDirectionScore = direction[0] * curDirection[0] +
1366 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001367 boolean exactDirectionOnly = false;
1368 boolean directionMatches = direction[0] == curDirection[0] &&
1369 direction[0] == curDirection[0];
1370 if ((directionMatches || !exactDirectionOnly) &&
1371 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001372 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1373 bestDistance = distance;
1374 bestDirectionScore = curDirectionScore;
1375 bestXY[0] = x;
1376 bestXY[1] = y;
1377 }
1378 }
1379 }
1380
1381 // Return -1, -1 if no suitable location found
1382 if (bestDistance == Float.MAX_VALUE) {
1383 bestXY[0] = -1;
1384 bestXY[1] = -1;
1385 }
1386 return bestXY;
1387 }
1388
1389 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001390 int[] direction, ItemConfiguration currentState) {
1391 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001392 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001393 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001394 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1395
Adam Cohen8baab352012-03-20 17:39:21 -07001396 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001397
1398 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001399 c.x = mTempLocation[0];
1400 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001401 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001402 }
Adam Cohen8baab352012-03-20 17:39:21 -07001403 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001404 return success;
1405 }
1406
Adam Cohenf3900c22012-11-16 18:28:11 -08001407 /**
1408 * This helper class defines a cluster of views. It helps with defining complex edges
1409 * of the cluster and determining how those edges interact with other views. The edges
1410 * essentially define a fine-grained boundary around the cluster of views -- like a more
1411 * precise version of a bounding box.
1412 */
1413 private class ViewCluster {
1414 final static int LEFT = 0;
1415 final static int TOP = 1;
1416 final static int RIGHT = 2;
1417 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001418
Adam Cohenf3900c22012-11-16 18:28:11 -08001419 ArrayList<View> views;
1420 ItemConfiguration config;
1421 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001422
Adam Cohenf3900c22012-11-16 18:28:11 -08001423 int[] leftEdge = new int[mCountY];
1424 int[] rightEdge = new int[mCountY];
1425 int[] topEdge = new int[mCountX];
1426 int[] bottomEdge = new int[mCountX];
1427 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1428
1429 @SuppressWarnings("unchecked")
1430 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1431 this.views = (ArrayList<View>) views.clone();
1432 this.config = config;
1433 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001434 }
1435
Adam Cohenf3900c22012-11-16 18:28:11 -08001436 void resetEdges() {
1437 for (int i = 0; i < mCountX; i++) {
1438 topEdge[i] = -1;
1439 bottomEdge[i] = -1;
1440 }
1441 for (int i = 0; i < mCountY; i++) {
1442 leftEdge[i] = -1;
1443 rightEdge[i] = -1;
1444 }
1445 leftEdgeDirty = true;
1446 rightEdgeDirty = true;
1447 bottomEdgeDirty = true;
1448 topEdgeDirty = true;
1449 boundingRectDirty = true;
1450 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001451
Adam Cohenf3900c22012-11-16 18:28:11 -08001452 void computeEdge(int which, int[] edge) {
1453 int count = views.size();
1454 for (int i = 0; i < count; i++) {
1455 CellAndSpan cs = config.map.get(views.get(i));
1456 switch (which) {
1457 case LEFT:
1458 int left = cs.x;
1459 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1460 if (left < edge[j] || edge[j] < 0) {
1461 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001462 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001463 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001464 break;
1465 case RIGHT:
1466 int right = cs.x + cs.spanX;
1467 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1468 if (right > edge[j]) {
1469 edge[j] = right;
1470 }
1471 }
1472 break;
1473 case TOP:
1474 int top = cs.y;
1475 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1476 if (top < edge[j] || edge[j] < 0) {
1477 edge[j] = top;
1478 }
1479 }
1480 break;
1481 case BOTTOM:
1482 int bottom = cs.y + cs.spanY;
1483 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1484 if (bottom > edge[j]) {
1485 edge[j] = bottom;
1486 }
1487 }
1488 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001489 }
1490 }
1491 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001492
1493 boolean isViewTouchingEdge(View v, int whichEdge) {
1494 CellAndSpan cs = config.map.get(v);
1495
1496 int[] edge = getEdge(whichEdge);
1497
1498 switch (whichEdge) {
1499 case LEFT:
1500 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1501 if (edge[i] == cs.x + cs.spanX) {
1502 return true;
1503 }
1504 }
1505 break;
1506 case RIGHT:
1507 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1508 if (edge[i] == cs.x) {
1509 return true;
1510 }
1511 }
1512 break;
1513 case TOP:
1514 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1515 if (edge[i] == cs.y + cs.spanY) {
1516 return true;
1517 }
1518 }
1519 break;
1520 case BOTTOM:
1521 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1522 if (edge[i] == cs.y) {
1523 return true;
1524 }
1525 }
1526 break;
1527 }
1528 return false;
1529 }
1530
1531 void shift(int whichEdge, int delta) {
1532 for (View v: views) {
1533 CellAndSpan c = config.map.get(v);
1534 switch (whichEdge) {
1535 case LEFT:
1536 c.x -= delta;
1537 break;
1538 case RIGHT:
1539 c.x += delta;
1540 break;
1541 case TOP:
1542 c.y -= delta;
1543 break;
1544 case BOTTOM:
1545 default:
1546 c.y += delta;
1547 break;
1548 }
1549 }
1550 resetEdges();
1551 }
1552
1553 public void addView(View v) {
1554 views.add(v);
1555 resetEdges();
1556 }
1557
1558 public Rect getBoundingRect() {
1559 if (boundingRectDirty) {
1560 boolean first = true;
1561 for (View v: views) {
1562 CellAndSpan c = config.map.get(v);
1563 if (first) {
1564 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1565 first = false;
1566 } else {
1567 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1568 }
1569 }
1570 }
1571 return boundingRect;
1572 }
1573
1574 public int[] getEdge(int which) {
1575 switch (which) {
1576 case LEFT:
1577 return getLeftEdge();
1578 case RIGHT:
1579 return getRightEdge();
1580 case TOP:
1581 return getTopEdge();
1582 case BOTTOM:
1583 default:
1584 return getBottomEdge();
1585 }
1586 }
1587
1588 public int[] getLeftEdge() {
1589 if (leftEdgeDirty) {
1590 computeEdge(LEFT, leftEdge);
1591 }
1592 return leftEdge;
1593 }
1594
1595 public int[] getRightEdge() {
1596 if (rightEdgeDirty) {
1597 computeEdge(RIGHT, rightEdge);
1598 }
1599 return rightEdge;
1600 }
1601
1602 public int[] getTopEdge() {
1603 if (topEdgeDirty) {
1604 computeEdge(TOP, topEdge);
1605 }
1606 return topEdge;
1607 }
1608
1609 public int[] getBottomEdge() {
1610 if (bottomEdgeDirty) {
1611 computeEdge(BOTTOM, bottomEdge);
1612 }
1613 return bottomEdge;
1614 }
1615
1616 PositionComparator comparator = new PositionComparator();
1617 class PositionComparator implements Comparator<View> {
1618 int whichEdge = 0;
1619 public int compare(View left, View right) {
1620 CellAndSpan l = config.map.get(left);
1621 CellAndSpan r = config.map.get(right);
1622 switch (whichEdge) {
1623 case LEFT:
1624 return (r.x + r.spanX) - (l.x + l.spanX);
1625 case RIGHT:
1626 return l.x - r.x;
1627 case TOP:
1628 return (r.y + r.spanY) - (l.y + l.spanY);
1629 case BOTTOM:
1630 default:
1631 return l.y - r.y;
1632 }
1633 }
1634 }
1635
1636 public void sortConfigurationForEdgePush(int edge) {
1637 comparator.whichEdge = edge;
1638 Collections.sort(config.sortedViews, comparator);
1639 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001640 }
1641
Adam Cohenf3900c22012-11-16 18:28:11 -08001642 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1643 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001644
Adam Cohenf3900c22012-11-16 18:28:11 -08001645 ViewCluster cluster = new ViewCluster(views, currentState);
1646 Rect clusterRect = cluster.getBoundingRect();
1647 int whichEdge;
1648 int pushDistance;
1649 boolean fail = false;
1650
1651 // Determine the edge of the cluster that will be leading the push and how far
1652 // the cluster must be shifted.
1653 if (direction[0] < 0) {
1654 whichEdge = ViewCluster.LEFT;
1655 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001656 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001657 whichEdge = ViewCluster.RIGHT;
1658 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1659 } else if (direction[1] < 0) {
1660 whichEdge = ViewCluster.TOP;
1661 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1662 } else {
1663 whichEdge = ViewCluster.BOTTOM;
1664 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001665 }
1666
Adam Cohenf3900c22012-11-16 18:28:11 -08001667 // Break early for invalid push distance.
1668 if (pushDistance <= 0) {
1669 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001670 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001671
1672 // Mark the occupied state as false for the group of views we want to move.
1673 for (View v: views) {
1674 CellAndSpan c = currentState.map.get(v);
1675 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1676 }
1677
1678 // We save the current configuration -- if we fail to find a solution we will revert
1679 // to the initial state. The process of finding a solution modifies the configuration
1680 // in place, hence the need for revert in the failure case.
1681 currentState.save();
1682
1683 // The pushing algorithm is simplified by considering the views in the order in which
1684 // they would be pushed by the cluster. For example, if the cluster is leading with its
1685 // left edge, we consider sort the views by their right edge, from right to left.
1686 cluster.sortConfigurationForEdgePush(whichEdge);
1687
1688 while (pushDistance > 0 && !fail) {
1689 for (View v: currentState.sortedViews) {
1690 // For each view that isn't in the cluster, we see if the leading edge of the
1691 // cluster is contacting the edge of that view. If so, we add that view to the
1692 // cluster.
1693 if (!cluster.views.contains(v) && v != dragView) {
1694 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1695 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1696 if (!lp.canReorder) {
1697 // The push solution includes the all apps button, this is not viable.
1698 fail = true;
1699 break;
1700 }
1701 cluster.addView(v);
1702 CellAndSpan c = currentState.map.get(v);
1703
1704 // Adding view to cluster, mark it as not occupied.
1705 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1706 }
1707 }
1708 }
1709 pushDistance--;
1710
1711 // The cluster has been completed, now we move the whole thing over in the appropriate
1712 // direction.
1713 cluster.shift(whichEdge, 1);
1714 }
1715
1716 boolean foundSolution = false;
1717 clusterRect = cluster.getBoundingRect();
1718
1719 // Due to the nature of the algorithm, the only check required to verify a valid solution
1720 // is to ensure that completed shifted cluster lies completely within the cell layout.
1721 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1722 clusterRect.bottom <= mCountY) {
1723 foundSolution = true;
1724 } else {
1725 currentState.restore();
1726 }
1727
1728 // In either case, we set the occupied array as marked for the location of the views
1729 for (View v: cluster.views) {
1730 CellAndSpan c = currentState.map.get(v);
1731 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1732 }
1733
1734 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001735 }
1736
Adam Cohen482ed822012-03-02 14:15:13 -08001737 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001738 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001739 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001740
Adam Cohen8baab352012-03-20 17:39:21 -07001741 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001742 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001743 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001744 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001745 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001746 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001747 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001748 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001749 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001750 }
1751 }
Adam Cohen8baab352012-03-20 17:39:21 -07001752
Adam Cohen8baab352012-03-20 17:39:21 -07001753 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001754 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001755 CellAndSpan c = currentState.map.get(v);
1756 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1757 }
1758
Adam Cohen47a876d2012-03-19 13:21:41 -07001759 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1760 int top = boundingRect.top;
1761 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001762 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001763 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001764 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001765 CellAndSpan c = currentState.map.get(v);
1766 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001767 }
1768
Adam Cohen482ed822012-03-02 14:15:13 -08001769 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1770
Adam Cohenf3900c22012-11-16 18:28:11 -08001771 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1772 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001773
Adam Cohen8baab352012-03-20 17:39:21 -07001774 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001775 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001776 int deltaX = mTempLocation[0] - boundingRect.left;
1777 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001778 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001779 CellAndSpan c = currentState.map.get(v);
1780 c.x += deltaX;
1781 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001782 }
1783 success = true;
1784 }
Adam Cohen8baab352012-03-20 17:39:21 -07001785
1786 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001787 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001788 CellAndSpan c = currentState.map.get(v);
1789 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001790 }
1791 return success;
1792 }
1793
1794 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1795 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1796 }
1797
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001798 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1799 // to push items in each of the cardinal directions, in an order based on the direction vector
1800 // passed.
1801 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1802 int[] direction, View ignoreView, ItemConfiguration solution) {
1803 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001804 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001805 // separately in each of the components.
1806 int temp = direction[1];
1807 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001808
1809 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001810 ignoreView, solution)) {
1811 return true;
1812 }
1813 direction[1] = temp;
1814 temp = direction[0];
1815 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001816
1817 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001818 ignoreView, solution)) {
1819 return true;
1820 }
1821 // Revert the direction
1822 direction[0] = temp;
1823
1824 // Now we try pushing in each component of the opposite direction
1825 direction[0] *= -1;
1826 direction[1] *= -1;
1827 temp = direction[1];
1828 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001829 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001830 ignoreView, solution)) {
1831 return true;
1832 }
1833
1834 direction[1] = temp;
1835 temp = direction[0];
1836 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001837 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001838 ignoreView, solution)) {
1839 return true;
1840 }
1841 // revert the direction
1842 direction[0] = temp;
1843 direction[0] *= -1;
1844 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001845
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001846 } else {
1847 // If the direction vector has a single non-zero component, we push first in the
1848 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001849 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001850 ignoreView, solution)) {
1851 return true;
1852 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001853 // Then we try the opposite direction
1854 direction[0] *= -1;
1855 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001856 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001857 ignoreView, solution)) {
1858 return true;
1859 }
1860 // Switch the direction back
1861 direction[0] *= -1;
1862 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001863
1864 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001865 // to find a solution by pushing along the perpendicular axis.
1866
1867 // Swap the components
1868 int temp = direction[1];
1869 direction[1] = direction[0];
1870 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001871 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001872 ignoreView, solution)) {
1873 return true;
1874 }
1875
1876 // Then we try the opposite direction
1877 direction[0] *= -1;
1878 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001879 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001880 ignoreView, solution)) {
1881 return true;
1882 }
1883 // Switch the direction back
1884 direction[0] *= -1;
1885 direction[1] *= -1;
1886
1887 // Swap the components back
1888 temp = direction[1];
1889 direction[1] = direction[0];
1890 direction[0] = temp;
1891 }
1892 return false;
1893 }
1894
Adam Cohen482ed822012-03-02 14:15:13 -08001895 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001896 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001897 // Return early if get invalid cell positions
1898 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001899
Adam Cohen8baab352012-03-20 17:39:21 -07001900 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001901 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001902
Adam Cohen8baab352012-03-20 17:39:21 -07001903 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001904 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001905 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001906 if (c != null) {
1907 c.x = cellX;
1908 c.y = cellY;
1909 }
Adam Cohen482ed822012-03-02 14:15:13 -08001910 }
Adam Cohen482ed822012-03-02 14:15:13 -08001911 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1912 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001913 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001914 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001915 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001916 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001917 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001918 if (Rect.intersects(r0, r1)) {
1919 if (!lp.canReorder) {
1920 return false;
1921 }
1922 mIntersectingViews.add(child);
1923 }
1924 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001925
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001926 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1927
Winson Chung5f8afe62013-08-12 16:19:28 -07001928 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001929 // we try to find a solution such that no displaced item travels through another item
1930 // without also displacing that item.
1931 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001932 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001933 return true;
1934 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001935
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001936 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001937 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001938 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001939 return true;
1940 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001941
Adam Cohen482ed822012-03-02 14:15:13 -08001942 // Ok, they couldn't move as a block, let's move them individually
1943 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001944 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001945 return false;
1946 }
1947 }
1948 return true;
1949 }
1950
1951 /*
1952 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1953 * the provided point and the provided cell
1954 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001955 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001956 double angle = Math.atan(((float) deltaY) / deltaX);
1957
1958 result[0] = 0;
1959 result[1] = 0;
1960 if (Math.abs(Math.cos(angle)) > 0.5f) {
1961 result[0] = (int) Math.signum(deltaX);
1962 }
1963 if (Math.abs(Math.sin(angle)) > 0.5f) {
1964 result[1] = (int) Math.signum(deltaY);
1965 }
1966 }
1967
Adam Cohen8baab352012-03-20 17:39:21 -07001968 private void copyOccupiedArray(boolean[][] occupied) {
1969 for (int i = 0; i < mCountX; i++) {
1970 for (int j = 0; j < mCountY; j++) {
1971 occupied[i][j] = mOccupied[i][j];
1972 }
1973 }
1974 }
1975
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001976 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001977 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1978 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001979 // Copy the current state into the solution. This solution will be manipulated as necessary.
1980 copyCurrentStateToSolution(solution, false);
1981 // Copy the current occupied array into the temporary occupied array. This array will be
1982 // manipulated as necessary to find a solution.
1983 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001984
1985 // We find the nearest cell into which we would place the dragged item, assuming there's
1986 // nothing in its way.
1987 int result[] = new int[2];
1988 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1989
1990 boolean success = false;
1991 // First we try the exact nearest position of the item being dragged,
1992 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001993 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1994 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001995
1996 if (!success) {
1997 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1998 // x, then 1 in y etc.
1999 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002000 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2001 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002002 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002003 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2004 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002005 }
2006 solution.isSolution = false;
2007 } else {
2008 solution.isSolution = true;
2009 solution.dragViewX = result[0];
2010 solution.dragViewY = result[1];
2011 solution.dragViewSpanX = spanX;
2012 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002013 }
2014 return solution;
2015 }
2016
2017 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002018 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002019 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002020 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002021 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002022 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002023 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002024 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002025 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002026 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002027 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002028 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002029 }
2030 }
2031
2032 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2033 for (int i = 0; i < mCountX; i++) {
2034 for (int j = 0; j < mCountY; j++) {
2035 mTmpOccupied[i][j] = false;
2036 }
2037 }
2038
Michael Jurkaa52570f2012-03-20 03:18:20 -07002039 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002040 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002041 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002042 if (child == dragView) continue;
2043 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002044 CellAndSpan c = solution.map.get(child);
2045 if (c != null) {
2046 lp.tmpCellX = c.x;
2047 lp.tmpCellY = c.y;
2048 lp.cellHSpan = c.spanX;
2049 lp.cellVSpan = c.spanY;
2050 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002051 }
2052 }
2053 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2054 solution.dragViewSpanY, mTmpOccupied, true);
2055 }
2056
2057 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2058 commitDragView) {
2059
2060 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2061 for (int i = 0; i < mCountX; i++) {
2062 for (int j = 0; j < mCountY; j++) {
2063 occupied[i][j] = false;
2064 }
2065 }
2066
Michael Jurkaa52570f2012-03-20 03:18:20 -07002067 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002068 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002069 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002070 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002071 CellAndSpan c = solution.map.get(child);
2072 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002073 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2074 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002075 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002076 }
2077 }
2078 if (commitDragView) {
2079 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2080 solution.dragViewSpanY, occupied, true);
2081 }
2082 }
2083
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002084
2085 // This method starts or changes the reorder preview animations
2086 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2087 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002088 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002089 for (int i = 0; i < childCount; i++) {
2090 View child = mShortcutsAndWidgets.getChildAt(i);
2091 if (child == dragView) continue;
2092 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002093 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2094 != null && !solution.intersectingViews.contains(child);
2095
Adam Cohen19f37922012-03-21 11:59:11 -07002096 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002097 if (c != null && !skip) {
2098 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2099 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002100 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002101 }
2102 }
2103 }
2104
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002105 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002106 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002107 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002108 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002109 float finalDeltaX;
2110 float finalDeltaY;
2111 float initDeltaX;
2112 float initDeltaY;
2113 float finalScale;
2114 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002115 int mode;
2116 boolean repeating = false;
2117 private static final int PREVIEW_DURATION = 300;
2118 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2119
2120 public static final int MODE_HINT = 0;
2121 public static final int MODE_PREVIEW = 1;
2122
Adam Cohene7587d22012-05-24 18:50:02 -07002123 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002124
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002125 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2126 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002127 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2128 final int x0 = mTmpPoint[0];
2129 final int y0 = mTmpPoint[1];
2130 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2131 final int x1 = mTmpPoint[0];
2132 final int y1 = mTmpPoint[1];
2133 final int dX = x1 - x0;
2134 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002135 finalDeltaX = 0;
2136 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002137 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002138 if (dX == dY && dX == 0) {
2139 } else {
2140 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002141 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002142 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002143 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002144 } else {
2145 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002146 finalDeltaX = (int) (- dir * Math.signum(dX) *
2147 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2148 finalDeltaY = (int) (- dir * Math.signum(dY) *
2149 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002150 }
2151 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002152 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002153 initDeltaX = child.getTranslationX();
2154 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002155 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002156 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002157 this.child = child;
2158 }
2159
Adam Cohend024f982012-05-23 18:26:45 -07002160 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002161 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002162 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002163 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002164 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002165 if (finalDeltaX == 0 && finalDeltaY == 0) {
2166 completeAnimationImmediately();
2167 return;
2168 }
Adam Cohen19f37922012-03-21 11:59:11 -07002169 }
Adam Cohend024f982012-05-23 18:26:45 -07002170 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002171 return;
2172 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002173 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002174 a = va;
Tony Wickham489fc562015-09-02 14:45:39 -07002175
2176 // Animations are disabled in power save mode, causing the repeated animation to jump
2177 // spastically between beginning and end states. Since this looks bad, we don't repeat
2178 // the animation in power save mode.
2179 PowerManager powerManager = (PowerManager) getContext()
2180 .getSystemService(Context.POWER_SERVICE);
2181 boolean powerSaverOn = Utilities.ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
2182 if (!powerSaverOn) {
2183 va.setRepeatMode(ValueAnimator.REVERSE);
2184 va.setRepeatCount(ValueAnimator.INFINITE);
2185 }
2186
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002187 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002188 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002189 va.addUpdateListener(new AnimatorUpdateListener() {
2190 @Override
2191 public void onAnimationUpdate(ValueAnimator animation) {
2192 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002193 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2194 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2195 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002196 child.setTranslationX(x);
2197 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002198 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002199 child.setScaleX(s);
2200 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002201 }
2202 });
2203 va.addListener(new AnimatorListenerAdapter() {
2204 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002205 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002206 initDeltaX = 0;
2207 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002208 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002209 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002210 }
2211 });
Adam Cohen19f37922012-03-21 11:59:11 -07002212 mShakeAnimators.put(child, this);
2213 va.start();
2214 }
2215
Adam Cohend024f982012-05-23 18:26:45 -07002216 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002217 if (a != null) {
2218 a.cancel();
2219 }
Adam Cohen19f37922012-03-21 11:59:11 -07002220 }
Adam Cohene7587d22012-05-24 18:50:02 -07002221
Adam Cohen091440a2015-03-18 14:16:05 -07002222 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002223 if (a != null) {
2224 a.cancel();
2225 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002226
Michael Jurka2ecf9952012-06-18 12:52:28 -07002227 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002228 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002229 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002230 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2231 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002232 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2233 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002234 );
2235 s.setDuration(REORDER_ANIMATION_DURATION);
2236 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2237 s.start();
2238 }
Adam Cohen19f37922012-03-21 11:59:11 -07002239 }
2240
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002241 private void completeAndClearReorderPreviewAnimations() {
2242 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002243 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002244 }
2245 mShakeAnimators.clear();
2246 }
2247
Adam Cohen482ed822012-03-02 14:15:13 -08002248 private void commitTempPlacement() {
2249 for (int i = 0; i < mCountX; i++) {
2250 for (int j = 0; j < mCountY; j++) {
2251 mOccupied[i][j] = mTmpOccupied[i][j];
2252 }
2253 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002254 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002255 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002256 View child = mShortcutsAndWidgets.getChildAt(i);
2257 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2258 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002259 // We do a null check here because the item info can be null in the case of the
2260 // AllApps button in the hotseat.
2261 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002262 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2263 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2264 info.requiresDbUpdate = true;
2265 }
Adam Cohen2acce882012-03-28 19:03:19 -07002266 info.cellX = lp.cellX = lp.tmpCellX;
2267 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002268 info.spanX = lp.cellHSpan;
2269 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002270 }
Adam Cohen482ed822012-03-02 14:15:13 -08002271 }
Adam Cohen2acce882012-03-28 19:03:19 -07002272 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002273 }
2274
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002275 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002276 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002277 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002278 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002279 lp.useTmpCoords = useTempCoords;
2280 }
2281 }
2282
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002283 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002284 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2285 int[] result = new int[2];
2286 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002287 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002288 resultSpan);
2289 if (result[0] >= 0 && result[1] >= 0) {
2290 copyCurrentStateToSolution(solution, false);
2291 solution.dragViewX = result[0];
2292 solution.dragViewY = result[1];
2293 solution.dragViewSpanX = resultSpan[0];
2294 solution.dragViewSpanY = resultSpan[1];
2295 solution.isSolution = true;
2296 } else {
2297 solution.isSolution = false;
2298 }
2299 return solution;
2300 }
2301
2302 public void prepareChildForDrag(View child) {
2303 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002304 }
2305
Adam Cohen19f37922012-03-21 11:59:11 -07002306 /* This seems like it should be obvious and straight-forward, but when the direction vector
2307 needs to match with the notion of the dragView pushing other views, we have to employ
2308 a slightly more subtle notion of the direction vector. The question is what two points is
2309 the vector between? The center of the dragView and its desired destination? Not quite, as
2310 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2311 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2312 or right, which helps make pushing feel right.
2313 */
2314 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2315 int spanY, View dragView, int[] resultDirection) {
2316 int[] targetDestination = new int[2];
2317
2318 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2319 Rect dragRect = new Rect();
2320 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2321 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2322
2323 Rect dropRegionRect = new Rect();
2324 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2325 dragView, dropRegionRect, mIntersectingViews);
2326
2327 int dropRegionSpanX = dropRegionRect.width();
2328 int dropRegionSpanY = dropRegionRect.height();
2329
2330 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2331 dropRegionRect.height(), dropRegionRect);
2332
2333 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2334 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2335
2336 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2337 deltaX = 0;
2338 }
2339 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2340 deltaY = 0;
2341 }
2342
2343 if (deltaX == 0 && deltaY == 0) {
2344 // No idea what to do, give a random direction.
2345 resultDirection[0] = 1;
2346 resultDirection[1] = 0;
2347 } else {
2348 computeDirectionVector(deltaX, deltaY, resultDirection);
2349 }
2350 }
2351
2352 // For a given cell and span, fetch the set of views intersecting the region.
2353 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2354 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2355 if (boundingRect != null) {
2356 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2357 }
2358 intersectingViews.clear();
2359 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2360 Rect r1 = new Rect();
2361 final int count = mShortcutsAndWidgets.getChildCount();
2362 for (int i = 0; i < count; i++) {
2363 View child = mShortcutsAndWidgets.getChildAt(i);
2364 if (child == dragView) continue;
2365 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2366 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2367 if (Rect.intersects(r0, r1)) {
2368 mIntersectingViews.add(child);
2369 if (boundingRect != null) {
2370 boundingRect.union(r1);
2371 }
2372 }
2373 }
2374 }
2375
2376 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2377 View dragView, int[] result) {
2378 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2379 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2380 mIntersectingViews);
2381 return !mIntersectingViews.isEmpty();
2382 }
2383
2384 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002385 completeAndClearReorderPreviewAnimations();
2386 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2387 final int count = mShortcutsAndWidgets.getChildCount();
2388 for (int i = 0; i < count; i++) {
2389 View child = mShortcutsAndWidgets.getChildAt(i);
2390 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2391 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2392 lp.tmpCellX = lp.cellX;
2393 lp.tmpCellY = lp.cellY;
2394 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2395 0, false, false);
2396 }
Adam Cohen19f37922012-03-21 11:59:11 -07002397 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002398 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002399 }
Adam Cohen19f37922012-03-21 11:59:11 -07002400 }
2401
Adam Cohenbebf0422012-04-11 18:06:28 -07002402 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2403 View dragView, int[] direction, boolean commit) {
2404 int[] pixelXY = new int[2];
2405 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2406
2407 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002408 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002409 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2410
2411 setUseTempCoords(true);
2412 if (swapSolution != null && swapSolution.isSolution) {
2413 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2414 // committing anything or animating anything as we just want to determine if a solution
2415 // exists
2416 copySolutionToTempState(swapSolution, dragView);
2417 setItemPlacementDirty(true);
2418 animateItemsToSolution(swapSolution, dragView, commit);
2419
2420 if (commit) {
2421 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002422 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002423 setItemPlacementDirty(false);
2424 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002425 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2426 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002427 }
2428 mShortcutsAndWidgets.requestLayout();
2429 }
2430 return swapSolution.isSolution;
2431 }
2432
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002433 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002434 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002435 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002436 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002437
2438 if (resultSpan == null) {
2439 resultSpan = new int[2];
2440 }
2441
Adam Cohen19f37922012-03-21 11:59:11 -07002442 // When we are checking drop validity or actually dropping, we don't recompute the
2443 // direction vector, since we want the solution to match the preview, and it's possible
2444 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002445 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2446 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002447 mDirectionVector[0] = mPreviousReorderDirection[0];
2448 mDirectionVector[1] = mPreviousReorderDirection[1];
2449 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002450 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2451 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2452 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002453 }
2454 } else {
2455 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2456 mPreviousReorderDirection[0] = mDirectionVector[0];
2457 mPreviousReorderDirection[1] = mDirectionVector[1];
2458 }
2459
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002460 // Find a solution involving pushing / displacing any items in the way
2461 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002462 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2463
2464 // We attempt the approach which doesn't shuffle views at all
2465 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2466 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2467
2468 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002469
2470 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2471 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002472 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2473 finalSolution = swapSolution;
2474 } else if (noShuffleSolution.isSolution) {
2475 finalSolution = noShuffleSolution;
2476 }
2477
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002478 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002479 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002480 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2481 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002482 result[0] = finalSolution.dragViewX;
2483 result[1] = finalSolution.dragViewY;
2484 resultSpan[0] = finalSolution.dragViewSpanX;
2485 resultSpan[1] = finalSolution.dragViewSpanY;
2486 } else {
2487 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2488 }
2489 return result;
2490 }
2491
Adam Cohen482ed822012-03-02 14:15:13 -08002492 boolean foundSolution = true;
2493 if (!DESTRUCTIVE_REORDER) {
2494 setUseTempCoords(true);
2495 }
2496
2497 if (finalSolution != null) {
2498 result[0] = finalSolution.dragViewX;
2499 result[1] = finalSolution.dragViewY;
2500 resultSpan[0] = finalSolution.dragViewSpanX;
2501 resultSpan[1] = finalSolution.dragViewSpanY;
2502
2503 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2504 // committing anything or animating anything as we just want to determine if a solution
2505 // exists
2506 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2507 if (!DESTRUCTIVE_REORDER) {
2508 copySolutionToTempState(finalSolution, dragView);
2509 }
2510 setItemPlacementDirty(true);
2511 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2512
Adam Cohen19f37922012-03-21 11:59:11 -07002513 if (!DESTRUCTIVE_REORDER &&
2514 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002515 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002516 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002517 setItemPlacementDirty(false);
2518 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002519 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2520 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002521 }
2522 }
2523 } else {
2524 foundSolution = false;
2525 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2526 }
2527
2528 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2529 setUseTempCoords(false);
2530 }
Adam Cohen482ed822012-03-02 14:15:13 -08002531
Michael Jurkaa52570f2012-03-20 03:18:20 -07002532 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002533 return result;
2534 }
2535
Adam Cohen19f37922012-03-21 11:59:11 -07002536 void setItemPlacementDirty(boolean dirty) {
2537 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002538 }
Adam Cohen19f37922012-03-21 11:59:11 -07002539 boolean isItemPlacementDirty() {
2540 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002541 }
2542
Adam Cohen091440a2015-03-18 14:16:05 -07002543 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002544 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002545 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2546 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002547 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002548 boolean isSolution = false;
2549 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2550
Adam Cohenf3900c22012-11-16 18:28:11 -08002551 void save() {
2552 // Copy current state into savedMap
2553 for (View v: map.keySet()) {
2554 map.get(v).copy(savedMap.get(v));
2555 }
2556 }
2557
2558 void restore() {
2559 // Restore current state from savedMap
2560 for (View v: savedMap.keySet()) {
2561 savedMap.get(v).copy(map.get(v));
2562 }
2563 }
2564
2565 void add(View v, CellAndSpan cs) {
2566 map.put(v, cs);
2567 savedMap.put(v, new CellAndSpan());
2568 sortedViews.add(v);
2569 }
2570
Adam Cohen482ed822012-03-02 14:15:13 -08002571 int area() {
2572 return dragViewSpanX * dragViewSpanY;
2573 }
Adam Cohen8baab352012-03-20 17:39:21 -07002574 }
2575
2576 private class CellAndSpan {
2577 int x, y;
2578 int spanX, spanY;
2579
Adam Cohenf3900c22012-11-16 18:28:11 -08002580 public CellAndSpan() {
2581 }
2582
2583 public void copy(CellAndSpan copy) {
2584 copy.x = x;
2585 copy.y = y;
2586 copy.spanX = spanX;
2587 copy.spanY = spanY;
2588 }
2589
Adam Cohen8baab352012-03-20 17:39:21 -07002590 public CellAndSpan(int x, int y, int spanX, int spanY) {
2591 this.x = x;
2592 this.y = y;
2593 this.spanX = spanX;
2594 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002595 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002596
2597 public String toString() {
2598 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2599 }
2600
Adam Cohen482ed822012-03-02 14:15:13 -08002601 }
2602
Adam Cohendf035382011-04-11 17:22:04 -07002603 /**
Adam Cohendf035382011-04-11 17:22:04 -07002604 * Find a starting cell position that will fit the given bounds nearest the requested
2605 * cell location. Uses Euclidean distance to score multiple vacant areas.
2606 *
2607 * @param pixelX The X location at which you want to search for a vacant area.
2608 * @param pixelY The Y location at which you want to search for a vacant area.
2609 * @param spanX Horizontal span of the object.
2610 * @param spanY Vertical span of the object.
2611 * @param ignoreView Considers space occupied by this view as unoccupied
2612 * @param result Previously returned value to possibly recycle.
2613 * @return The X, Y cell of a vacant area that can contain this object,
2614 * nearest the requested location.
2615 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002616 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2617 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002618 }
2619
Michael Jurka0280c3b2010-09-17 15:00:07 -07002620 boolean existsEmptyCell() {
2621 return findCellForSpan(null, 1, 1);
2622 }
2623
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002624 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002625 * Finds the upper-left coordinate of the first rectangle in the grid that can
2626 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2627 * then this method will only return coordinates for rectangles that contain the cell
2628 * (intersectX, intersectY)
2629 *
2630 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2631 * can be found.
2632 * @param spanX The horizontal span of the cell we want to find.
2633 * @param spanY The vertical span of the cell we want to find.
2634 *
2635 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002636 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002637 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002638 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002639 final int endX = mCountX - (spanX - 1);
2640 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002641
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002642 for (int y = 0; y < endY && !foundCell; y++) {
2643 inner:
2644 for (int x = 0; x < endX; x++) {
2645 for (int i = 0; i < spanX; i++) {
2646 for (int j = 0; j < spanY; j++) {
2647 if (mOccupied[x + i][y + j]) {
2648 // small optimization: we can skip to after the column we just found
2649 // an occupied cell
2650 x += i;
2651 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002652 }
2653 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002654 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002655 if (cellXY != null) {
2656 cellXY[0] = x;
2657 cellXY[1] = y;
2658 }
2659 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002660 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002661 }
2662 }
2663
Michael Jurka28750fb2010-09-24 17:43:49 -07002664 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002665 }
2666
2667 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002668 * A drag event has begun over this layout.
2669 * It may have begun over this layout (in which case onDragChild is called first),
2670 * or it may have begun on another layout.
2671 */
2672 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002673 mDragging = true;
2674 }
2675
2676 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002677 * Called when drag has left this CellLayout or has been completed (successfully or not)
2678 */
2679 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002680 // This can actually be called when we aren't in a drag, e.g. when adding a new
2681 // item to this layout via the customize drawer.
2682 // Guard against that case.
2683 if (mDragging) {
2684 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002685 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002686
2687 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002688 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002689 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2690 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002691 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002692 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002693 }
2694
2695 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002696 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002697 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002698 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699 *
2700 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002701 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002702 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002703 if (child != null) {
2704 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002705 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002706 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002707 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002708 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002709 }
2710
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002711 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002712 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002713 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002714 * @param cellX X coordinate of upper left corner expressed as a cell position
2715 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002716 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002717 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002718 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002719 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002720 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002721 final int cellWidth = mCellWidth;
2722 final int cellHeight = mCellHeight;
2723 final int widthGap = mWidthGap;
2724 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002725
Winson Chung4b825dcd2011-06-19 12:41:22 -07002726 final int hStartPadding = getPaddingLeft();
2727 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002728
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002729 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2730 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2731
2732 int x = hStartPadding + cellX * (cellWidth + widthGap);
2733 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002734
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002735 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002736 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002737
Michael Jurka0280c3b2010-09-17 15:00:07 -07002738 private void clearOccupiedCells() {
2739 for (int x = 0; x < mCountX; x++) {
2740 for (int y = 0; y < mCountY; y++) {
2741 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002742 }
2743 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002744 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002745
Adam Cohend4844c32011-02-18 19:25:06 -08002746 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002747 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002748 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002749 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002750 }
2751
Adam Cohend4844c32011-02-18 19:25:06 -08002752 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002753 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002754 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002755 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002756 }
2757
Adam Cohen482ed822012-03-02 14:15:13 -08002758 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2759 boolean value) {
2760 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002761 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2762 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002763 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002764 }
2765 }
2766 }
2767
Adam Cohen2801caf2011-05-13 20:57:39 -07002768 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002769 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002770 (Math.max((mCountX - 1), 0) * mWidthGap);
2771 }
2772
2773 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002774 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002775 (Math.max((mCountY - 1), 0) * mHeightGap);
2776 }
2777
Michael Jurka66d72172011-04-12 16:29:25 -07002778 public boolean isOccupied(int x, int y) {
2779 if (x < mCountX && y < mCountY) {
2780 return mOccupied[x][y];
2781 } else {
2782 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2783 }
2784 }
2785
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002786 @Override
2787 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2788 return new CellLayout.LayoutParams(getContext(), attrs);
2789 }
2790
2791 @Override
2792 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2793 return p instanceof CellLayout.LayoutParams;
2794 }
2795
2796 @Override
2797 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2798 return new CellLayout.LayoutParams(p);
2799 }
2800
2801 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2802 /**
2803 * Horizontal location of the item in the grid.
2804 */
2805 @ViewDebug.ExportedProperty
2806 public int cellX;
2807
2808 /**
2809 * Vertical location of the item in the grid.
2810 */
2811 @ViewDebug.ExportedProperty
2812 public int cellY;
2813
2814 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002815 * Temporary horizontal location of the item in the grid during reorder
2816 */
2817 public int tmpCellX;
2818
2819 /**
2820 * Temporary vertical location of the item in the grid during reorder
2821 */
2822 public int tmpCellY;
2823
2824 /**
2825 * Indicates that the temporary coordinates should be used to layout the items
2826 */
2827 public boolean useTmpCoords;
2828
2829 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002830 * Number of cells spanned horizontally by the item.
2831 */
2832 @ViewDebug.ExportedProperty
2833 public int cellHSpan;
2834
2835 /**
2836 * Number of cells spanned vertically by the item.
2837 */
2838 @ViewDebug.ExportedProperty
2839 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002840
Adam Cohen1b607ed2011-03-03 17:26:50 -08002841 /**
2842 * Indicates whether the item will set its x, y, width and height parameters freely,
2843 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2844 */
Adam Cohend4844c32011-02-18 19:25:06 -08002845 public boolean isLockedToGrid = true;
2846
Adam Cohen482ed822012-03-02 14:15:13 -08002847 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002848 * Indicates that this item should use the full extents of its parent.
2849 */
2850 public boolean isFullscreen = false;
2851
2852 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002853 * Indicates whether this item can be reordered. Always true except in the case of the
2854 * the AllApps button.
2855 */
2856 public boolean canReorder = true;
2857
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002858 // X coordinate of the view in the layout.
2859 @ViewDebug.ExportedProperty
2860 int x;
2861 // Y coordinate of the view in the layout.
2862 @ViewDebug.ExportedProperty
2863 int y;
2864
Romain Guy84f296c2009-11-04 15:00:44 -08002865 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002866
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002867 public LayoutParams(Context c, AttributeSet attrs) {
2868 super(c, attrs);
2869 cellHSpan = 1;
2870 cellVSpan = 1;
2871 }
2872
2873 public LayoutParams(ViewGroup.LayoutParams source) {
2874 super(source);
2875 cellHSpan = 1;
2876 cellVSpan = 1;
2877 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002878
2879 public LayoutParams(LayoutParams source) {
2880 super(source);
2881 this.cellX = source.cellX;
2882 this.cellY = source.cellY;
2883 this.cellHSpan = source.cellHSpan;
2884 this.cellVSpan = source.cellVSpan;
2885 }
2886
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002887 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002888 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002889 this.cellX = cellX;
2890 this.cellY = cellY;
2891 this.cellHSpan = cellHSpan;
2892 this.cellVSpan = cellVSpan;
2893 }
2894
Adam Cohen2374abf2013-04-16 14:56:57 -07002895 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2896 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002897 if (isLockedToGrid) {
2898 final int myCellHSpan = cellHSpan;
2899 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002900 int myCellX = useTmpCoords ? tmpCellX : cellX;
2901 int myCellY = useTmpCoords ? tmpCellY : cellY;
2902
2903 if (invertHorizontally) {
2904 myCellX = colCount - myCellX - cellHSpan;
2905 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002906
Adam Cohend4844c32011-02-18 19:25:06 -08002907 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2908 leftMargin - rightMargin;
2909 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2910 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002911 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2912 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002913 }
2914 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002915
Winson Chungaafa03c2010-06-11 17:34:16 -07002916 public String toString() {
2917 return "(" + this.cellX + ", " + this.cellY + ")";
2918 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002919
2920 public void setWidth(int width) {
2921 this.width = width;
2922 }
2923
2924 public int getWidth() {
2925 return width;
2926 }
2927
2928 public void setHeight(int height) {
2929 this.height = height;
2930 }
2931
2932 public int getHeight() {
2933 return height;
2934 }
2935
2936 public void setX(int x) {
2937 this.x = x;
2938 }
2939
2940 public int getX() {
2941 return x;
2942 }
2943
2944 public void setY(int y) {
2945 this.y = y;
2946 }
2947
2948 public int getY() {
2949 return y;
2950 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002951 }
2952
Michael Jurka0280c3b2010-09-17 15:00:07 -07002953 // This class stores info for two purposes:
2954 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2955 // its spanX, spanY, and the screen it is on
2956 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2957 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2958 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002959 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002960 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002961 int cellX = -1;
2962 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002963 int spanX;
2964 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002965 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002966 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002967
Sunny Goyal83a8f042015-05-19 12:52:12 -07002968 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002969 cell = v;
2970 cellX = info.cellX;
2971 cellY = info.cellY;
2972 spanX = info.spanX;
2973 spanY = info.spanY;
2974 screenId = info.screenId;
2975 container = info.container;
2976 }
2977
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002978 @Override
2979 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002980 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2981 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002982 }
2983 }
Michael Jurkad771c962011-08-09 15:00:48 -07002984
Sunny Goyala9116722015-04-29 13:55:58 -07002985 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2986 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2987 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002988
2989 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
2990 int x2 = x + spanX - 1;
2991 int y2 = y + spanY - 1;
2992 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
2993 return false;
2994 }
2995 for (int i = x; i <= x2; i++) {
2996 for (int j = y; j <= y2; j++) {
2997 if (mOccupied[i][j]) {
2998 return false;
2999 }
3000 }
3001 }
3002
3003 return true;
3004 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003005}