blob: 40cdc8016c2302b7ca15bd53e49a68e557ab1b1f [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;
Chet Haase00397b12010-10-07 11:13:10 -070021import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohenc9735cf2015-01-23 16:11:55 -080024import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040026import android.content.res.Resources;
Joe Onorato4be866d2010-10-10 11:26:02 -070027import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080029import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070030import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070031import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080032import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080033import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070034import android.graphics.drawable.Drawable;
Sunny Goyal2805e632015-05-20 15:35:32 -070035import android.graphics.drawable.TransitionDrawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080036import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070037import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080038import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080039import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070041import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewDebug;
45import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080046import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070047import android.view.animation.DecelerateInterpolator;
Tony Wickham9e0702f2015-09-02 14:45:39 -070048import android.widget.Toast;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049
Sunny Goyal4b6eb262015-05-14 19:24:40 -070050import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Daniel Sandler325dc232013-06-05 22:57:57 -040051import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyalaa8ef112015-06-12 20:04:41 -070052import com.android.launcher3.LauncherSettings.Favorites;
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 Goyal6c56c682015-07-16 14:09:05 -070056import com.android.launcher3.config.ProviderConfig;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070057import com.android.launcher3.util.ParcelableSparseArray;
Adam Cohen091440a2015-03-18 14:16:05 -070058import com.android.launcher3.util.Thunk;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070059
Adam Cohen69ce2e52011-07-03 19:25:21 -070060import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070061import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080062import java.util.Collections;
63import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070064import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080065import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070066
Sunny Goyal4b6eb262015-05-14 19:24:40 -070067public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070068 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
69 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
70
Tony Wickhama0628cc2015-10-14 15:23:04 -070071 private static final String TAG = "CellLayout";
72 private static final boolean LOGD = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070073
Adam Cohen2acce882012-03-28 19:03:19 -070074 private Launcher mLauncher;
Sunny Goyal4ffec482016-02-09 11:28:52 -080075 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070076 @Thunk int mCellWidth;
Sunny Goyal4ffec482016-02-09 11:28:52 -080077 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070078 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070079 private int mFixedCellWidth;
80 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070081
Sunny Goyal4ffec482016-02-09 11:28:52 -080082 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070083 @Thunk int mCountX;
Sunny Goyal4ffec482016-02-09 11:28:52 -080084 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070085 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086
Adam Cohen234c4cd2011-07-17 21:03:04 -070087 private int mOriginalWidthGap;
88 private int mOriginalHeightGap;
Sunny Goyal4ffec482016-02-09 11:28:52 -080089 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070090 @Thunk int mWidthGap;
Sunny Goyal4ffec482016-02-09 11:28:52 -080091 @ViewDebug.ExportedProperty(category = "launcher")
Adam Cohen091440a2015-03-18 14:16:05 -070092 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070093 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070094 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070095 private boolean mIsDragTarget = true;
Sunny Goyale2fd14b2015-08-27 17:45:46 -070096 private boolean mJailContent = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080097
Patrick Dubroyde7658b2010-09-27 11:15:43 -070098 // These are temporary variables to prevent having to allocate a new object just to
99 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -0700100 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -0700101 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700102
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800103 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -0800104 boolean[][] mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800105
Michael Jurkadee05892010-07-27 10:01:56 -0700106 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -0700107 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -0700108
Adam Cohen69ce2e52011-07-03 19:25:21 -0700109 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700110 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700111
Michael Jurka5f1c5092010-09-03 14:15:02 -0700112 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700113
Sunny Goyal2805e632015-05-20 15:35:32 -0700114 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
115 private final TransitionDrawable mBackground;
116
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700117 // These values allow a fixed measurement to be set on the CellLayout.
118 private int mFixedWidth = -1;
119 private int mFixedHeight = -1;
120
Michael Jurka33945b22010-12-21 18:19:38 -0800121 // If we're actively dragging something over this screen, mIsDragOverlapping is true
122 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700123
Winson Chung150fbab2010-09-29 17:14:26 -0700124 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700125 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700126 @Thunk Rect[] mDragOutlines = new Rect[4];
127 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700128 private InterruptibleInOutAnimator[] mDragOutlineAnims =
129 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700130
131 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700132 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700133 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700134
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700135 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800136
Sunny Goyal316490e2015-06-02 09:38:28 -0700137 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
138 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700139
140 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700141
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700142 // When a drag operation is in progress, holds the nearest cell to the touch point
143 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800144
Joe Onorato4be866d2010-10-10 11:26:02 -0700145 private boolean mDragging = false;
146
Patrick Dubroyce34a972010-10-19 10:34:32 -0700147 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700148 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700149
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800150 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700151 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800152
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800153 public static final int MODE_SHOW_REORDER_HINT = 0;
154 public static final int MODE_DRAG_OVER = 1;
155 public static final int MODE_ON_DROP = 2;
156 public static final int MODE_ON_DROP_EXTERNAL = 3;
157 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700158 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800159 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
160
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800161 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700162 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700163 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700164
Adam Cohen482ed822012-03-02 14:15:13 -0800165 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
166 private Rect mOccupiedRect = new Rect();
167 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700168 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700169 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800170
Sunny Goyal2805e632015-05-20 15:35:32 -0700171 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700172
Michael Jurkaca993832012-06-29 15:17:04 -0700173 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700174
Adam Cohenc9735cf2015-01-23 16:11:55 -0800175 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700176 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800177 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800178
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800179 public CellLayout(Context context) {
180 this(context, null);
181 }
182
183 public CellLayout(Context context, AttributeSet attrs) {
184 this(context, attrs, 0);
185 }
186
187 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
188 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700189
190 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
191 // the user where a dragged item will land when dropped.
192 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800193 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700194 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700195
Adam Cohen2e6da152015-05-06 11:42:25 -0700196 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800197
Winson Chung11a1a532013-09-13 11:14:45 -0700198 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800199 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700200 mWidthGap = mOriginalWidthGap = 0;
201 mHeightGap = mOriginalHeightGap = 0;
202 mMaxGap = Integer.MAX_VALUE;
Adam Cohen2e6da152015-05-06 11:42:25 -0700203 mCountX = (int) grid.inv.numColumns;
204 mCountY = (int) grid.inv.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700205 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800206 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700207 mPreviousReorderDirection[0] = INVALID_DIRECTION;
208 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800209
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800210 setAlwaysDrawnWithCacheEnabled(false);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700211 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700212 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700213
Sunny Goyal2805e632015-05-20 15:35:32 -0700214 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
215 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700216 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800217
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800218 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700219 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700220
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700221 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700222 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700223 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700224 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800225 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700226 }
227
228 // When dragging things around the home screens, we show a green outline of
229 // where the item will land. The outlines gradually fade out, leaving a trail
230 // behind the drag path.
231 // Set up all the animations that are used to implement this fading.
232 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700233 final float fromAlphaValue = 0;
234 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700235
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700236 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700237
238 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700239 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100240 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700241 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700242 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700243 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700244 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700245 final Bitmap outline = (Bitmap)anim.getTag();
246
247 // If an animation is started and then stopped very quickly, we can still
248 // get spurious updates we've cleared the tag. Guard against this.
249 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700250 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700251 Object val = animation.getAnimatedValue();
252 Log.d(TAG, "anim " + thisIndex + " update: " + val +
253 ", isStopped " + anim.isStopped());
254 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700255 // Try to prevent it from continuing to run
256 animation.cancel();
257 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700258 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800259 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700260 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700261 }
262 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700263 // The animation holds a reference to the drag outline bitmap as long is it's
264 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700265 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700266 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700268 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 anim.setTag(null);
270 }
271 }
272 });
273 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700274 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700275
Michael Jurkaa52570f2012-03-20 03:18:20 -0700276 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700277 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700278 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700279
Mady Mellorbb835202015-07-15 16:34:34 -0700280 mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
Mady Melloref044dd2015-06-02 15:35:07 -0700281
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700282 mTouchFeedbackView = new ClickShadowView(context);
283 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700284 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700285 }
286
Adam Cohenc9735cf2015-01-23 16:11:55 -0800287 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700288 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800289 mUseTouchHelper = enable;
290 if (!enable) {
291 ViewCompat.setAccessibilityDelegate(this, null);
292 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
293 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
294 setOnClickListener(mLauncher);
295 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700296 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
297 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
298 mTouchHelper = new WorkspaceAccessibilityHelper(this);
299 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
300 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
301 mTouchHelper = new FolderAccessibilityHelper(this);
302 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800303 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
304 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
305 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
306 setOnClickListener(mTouchHelper);
307 }
308
309 // Invalidate the accessibility hierarchy
310 if (getParent() != null) {
311 getParent().notifySubtreeAccessibilityStateChanged(
312 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
313 }
314 }
315
316 @Override
317 public boolean dispatchHoverEvent(MotionEvent event) {
318 // Always attempt to dispatch hover events to accessibility first.
319 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
320 return true;
321 }
322 return super.dispatchHoverEvent(event);
323 }
324
325 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800326 public boolean onInterceptTouchEvent(MotionEvent ev) {
327 if (mUseTouchHelper ||
328 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
329 return true;
330 }
331 return false;
332 }
333
Mady Melloref044dd2015-06-02 15:35:07 -0700334 @Override
335 public boolean onTouchEvent(MotionEvent ev) {
336 boolean handled = super.onTouchEvent(ev);
337 // Stylus button press on a home screen should not switch between overview mode and
338 // the home screen mode, however, once in overview mode stylus button press should be
339 // enabled to allow rearranging the different home screens. So check what mode
340 // the workspace is in, and only perform stylus button presses while in overview mode.
341 if (mLauncher.mWorkspace.isInOverviewMode()
Mady Mellorbb835202015-07-15 16:34:34 -0700342 && mStylusEventHelper.onMotionEvent(ev)) {
Mady Melloref044dd2015-06-02 15:35:07 -0700343 return true;
344 }
345 return handled;
346 }
347
Chris Craik01f2d7f2013-10-01 14:41:56 -0700348 public void enableHardwareLayer(boolean hasLayer) {
349 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700350 }
351
352 public void buildHardwareLayer() {
353 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700354 }
355
Adam Cohen307fe232012-08-16 17:55:58 -0700356 public float getChildrenScale() {
357 return mIsHotseat ? mHotseatScale : 1.0f;
358 }
359
Winson Chung5f8afe62013-08-12 16:19:28 -0700360 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700361 mFixedCellWidth = mCellWidth = width;
362 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700363 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
364 mCountX, mCountY);
365 }
366
Adam Cohen2801caf2011-05-13 20:57:39 -0700367 public void setGridSize(int x, int y) {
368 mCountX = x;
369 mCountY = y;
370 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800371 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700372 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700373 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700374 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700375 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700376 }
377
Adam Cohen2374abf2013-04-16 14:56:57 -0700378 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
379 public void setInvertIfRtl(boolean invert) {
380 mShortcutsAndWidgets.setInvertIfRtl(invert);
381 }
382
Adam Cohen917e3882013-10-31 15:03:35 -0700383 public void setDropPending(boolean pending) {
384 mDropPending = pending;
385 }
386
387 public boolean isDropPending() {
388 return mDropPending;
389 }
390
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700391 @Override
392 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700393 if (icon == null || background == null) {
394 mTouchFeedbackView.setBitmap(null);
395 mTouchFeedbackView.animate().cancel();
396 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700397 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700398 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
399 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700400 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800401 }
402 }
403
Adam Cohenc50438c2014-08-19 17:43:05 -0700404 void disableDragTarget() {
405 mIsDragTarget = false;
406 }
407
Tony Wickham0f97b782015-12-02 17:55:07 -0800408 public boolean isDragTarget() {
409 return mIsDragTarget;
410 }
411
Adam Cohenc50438c2014-08-19 17:43:05 -0700412 void setIsDragOverlapping(boolean isDragOverlapping) {
413 if (mIsDragOverlapping != isDragOverlapping) {
414 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700415 if (mIsDragOverlapping) {
416 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
417 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700418 if (mBackgroundAlpha > 0f) {
419 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
420 } else {
421 mBackground.resetTransition();
422 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700423 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700424 invalidate();
425 }
426 }
427
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700428 public void disableJailContent() {
429 mJailContent = false;
430 }
431
432 @Override
433 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
434 if (mJailContent) {
435 ParcelableSparseArray jail = getJailedArray(container);
436 super.dispatchSaveInstanceState(jail);
437 container.put(R.id.cell_layout_jail_id, jail);
438 } else {
439 super.dispatchSaveInstanceState(container);
440 }
441 }
442
443 @Override
444 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
445 super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
446 }
447
448 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
449 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
450 return parcelable instanceof ParcelableSparseArray ?
451 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
452 }
453
Tony Wickham0f97b782015-12-02 17:55:07 -0800454 public boolean getIsDragOverlapping() {
455 return mIsDragOverlapping;
456 }
457
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700458 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700459 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700460 if (!mIsDragTarget) {
461 return;
462 }
463
Michael Jurka3e7c7632010-10-02 16:01:03 -0700464 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
465 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
466 // When we're small, we are either drawn normally or in the "accepts drops" state (during
467 // a drag). However, we also drag the mini hover background *over* one of those two
468 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700469 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700470 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700471 }
Romain Guya6abce82009-11-10 02:54:41 -0800472
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700473 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700474 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700475 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700476 if (alpha > 0) {
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));
Sunny Goyal106bf642015-07-16 12:18:06 -0700479 canvas.drawBitmap(b, null, mDragOutlines[i], 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) {
Sunny Goyal6c56c682015-07-16 14:09:05 -0700594 if (ProviderConfig.IS_DOGFOOD_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700595 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);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700657 if (LOGD) {
658 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
659 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700660 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700661
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700662 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700663
Winson Chungaafa03c2010-06-11 17:34:16 -0700664 return true;
665 }
666 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800667 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700668
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800669 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700670 public void removeAllViews() {
671 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700672 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700673 }
674
675 @Override
676 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700677 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700678 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700679 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700680 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700681 }
682
683 @Override
684 public void removeView(View view) {
685 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700686 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700687 }
688
689 @Override
690 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700691 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
692 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700693 }
694
695 @Override
696 public void removeViewInLayout(View view) {
697 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700698 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700699 }
700
701 @Override
702 public void removeViews(int start, int count) {
703 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700704 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700705 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700706 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700707 }
708
709 @Override
710 public void removeViewsInLayout(int start, int count) {
711 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700712 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700713 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700714 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800715 }
716
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700717 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700718 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800719 * @param x X coordinate of the point
720 * @param y Y coordinate of the point
721 * @param result Array of 2 ints to hold the x and y coordinate of the cell
722 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700723 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700724 final int hStartPadding = getPaddingLeft();
725 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800726
727 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
728 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
729
Adam Cohend22015c2010-07-26 22:02:18 -0700730 final int xAxis = mCountX;
731 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800732
733 if (result[0] < 0) result[0] = 0;
734 if (result[0] >= xAxis) result[0] = xAxis - 1;
735 if (result[1] < 0) result[1] = 0;
736 if (result[1] >= yAxis) result[1] = yAxis - 1;
737 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700738
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800739 /**
740 * Given a point, return the cell that most closely encloses that point
741 * @param x X coordinate of the point
742 * @param y Y coordinate of the point
743 * @param result Array of 2 ints to hold the x and y coordinate of the cell
744 */
745 void pointToCellRounded(int x, int y, int[] result) {
746 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
747 }
748
749 /**
750 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700751 *
752 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800753 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700754 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800755 * @param result Array of 2 ints to hold the x and y coordinate of the point
756 */
757 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700758 final int hStartPadding = getPaddingLeft();
759 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800760
761 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
762 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
763 }
764
Adam Cohene3e27a82011-04-15 12:07:39 -0700765 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800766 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700767 *
768 * @param cellX X coordinate of the cell
769 * @param cellY Y coordinate of the cell
770 *
771 * @param result Array of 2 ints to hold the x and y coordinate of the point
772 */
773 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700774 regionToCenterPoint(cellX, cellY, 1, 1, result);
775 }
776
777 /**
778 * Given a cell coordinate and span return the point that represents the center of the regio
779 *
780 * @param cellX X coordinate of the cell
781 * @param cellY Y coordinate of the cell
782 *
783 * @param result Array of 2 ints to hold the x and y coordinate of the point
784 */
785 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700786 final int hStartPadding = getPaddingLeft();
787 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700788 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
789 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
790 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
791 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700792 }
793
Adam Cohen19f37922012-03-21 11:59:11 -0700794 /**
795 * Given a cell coordinate and span fills out a corresponding pixel rect
796 *
797 * @param cellX X coordinate of the cell
798 * @param cellY Y coordinate of the cell
799 * @param result Rect in which to write the result
800 */
801 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
802 final int hStartPadding = getPaddingLeft();
803 final int vStartPadding = getPaddingTop();
804 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
805 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
806 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
807 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
808 }
809
Adam Cohen482ed822012-03-02 14:15:13 -0800810 public float getDistanceFromCell(float x, float y, int[] cell) {
811 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700812 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800813 }
814
Romain Guy84f296c2009-11-04 15:00:44 -0800815 int getCellWidth() {
816 return mCellWidth;
817 }
818
819 int getCellHeight() {
820 return mCellHeight;
821 }
822
Adam Cohend4844c32011-02-18 19:25:06 -0800823 int getWidthGap() {
824 return mWidthGap;
825 }
826
827 int getHeightGap() {
828 return mHeightGap;
829 }
830
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700831 public void setFixedSize(int width, int height) {
832 mFixedWidth = width;
833 mFixedHeight = height;
834 }
835
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800836 @Override
837 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800838 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800839 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700840 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
841 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700842 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
843 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700844 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700845 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
846 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700847 if (cw != mCellWidth || ch != mCellHeight) {
848 mCellWidth = cw;
849 mCellHeight = ch;
850 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
851 mHeightGap, mCountX, mCountY);
852 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700853 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700854
Winson Chung2d75f122013-09-23 16:53:31 -0700855 int newWidth = childWidthSize;
856 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700857 if (mFixedWidth > 0 && mFixedHeight > 0) {
858 newWidth = mFixedWidth;
859 newHeight = mFixedHeight;
860 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800861 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
862 }
863
Adam Cohend22015c2010-07-26 22:02:18 -0700864 int numWidthGaps = mCountX - 1;
865 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800866
Adam Cohen234c4cd2011-07-17 21:03:04 -0700867 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700868 int hSpace = childWidthSize;
869 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700870 int hFreeSpace = hSpace - (mCountX * mCellWidth);
871 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700872 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
873 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700874 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
875 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700876 } else {
877 mWidthGap = mOriginalWidthGap;
878 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700879 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700880
881 // Make the feedback view large enough to hold the blur bitmap.
882 mTouchFeedbackView.measure(
883 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
884 MeasureSpec.EXACTLY),
885 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
886 MeasureSpec.EXACTLY));
887
888 mShortcutsAndWidgets.measure(
889 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
890 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
891
892 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
893 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700894 if (mFixedWidth > 0 && mFixedHeight > 0) {
895 setMeasuredDimension(maxWidth, maxHeight);
896 } else {
897 setMeasuredDimension(widthSize, heightSize);
898 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800899 }
900
901 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700902 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800903 boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
904 ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
905 int left = getPaddingLeft();
906 if (!isFullscreen) {
Tony Wickhama501d492015-11-03 18:05:01 -0800907 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Tony Wickham26b01422015-11-10 14:44:32 -0800908 }
Winson Chung38848ca2013-10-08 12:03:44 -0700909 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700910
911 mTouchFeedbackView.layout(left, top,
912 left + mTouchFeedbackView.getMeasuredWidth(),
913 top + mTouchFeedbackView.getMeasuredHeight());
914 mShortcutsAndWidgets.layout(left, top,
915 left + r - l,
916 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800917 }
918
Tony Wickhama501d492015-11-03 18:05:01 -0800919 /**
920 * Returns the amount of space left over after subtracting padding and cells. This space will be
921 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
922 * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
923 */
924 public int getUnusedHorizontalSpace() {
925 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
926 }
927
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800928 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700929 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
930 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700931
932 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700933 mBackground.getPadding(mTempRect);
934 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
935 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700936 }
937
938 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800939 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700940 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800941 }
942
943 @Override
944 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700945 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800946 }
947
Michael Jurka5f1c5092010-09-03 14:15:02 -0700948 public float getBackgroundAlpha() {
949 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700950 }
951
Michael Jurka5f1c5092010-09-03 14:15:02 -0700952 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800953 if (mBackgroundAlpha != alpha) {
954 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700955 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800956 }
Michael Jurkadee05892010-07-27 10:01:56 -0700957 }
958
Sunny Goyal2805e632015-05-20 15:35:32 -0700959 @Override
960 protected boolean verifyDrawable(Drawable who) {
961 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
962 }
963
Michael Jurkaa52570f2012-03-20 03:18:20 -0700964 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700965 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700966 }
967
Michael Jurkaa52570f2012-03-20 03:18:20 -0700968 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700969 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700970 }
971
Patrick Dubroy440c3602010-07-13 17:50:32 -0700972 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700973 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700974 }
975
Adam Cohen76fc0852011-06-17 13:26:23 -0700976 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800977 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700978 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800979 boolean[][] occupied = mOccupied;
980 if (!permanent) {
981 occupied = mTmpOccupied;
982 }
983
Adam Cohen19f37922012-03-21 11:59:11 -0700984 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700985 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
986 final ItemInfo info = (ItemInfo) child.getTag();
987
988 // We cancel any existing animations
989 if (mReorderAnimators.containsKey(lp)) {
990 mReorderAnimators.get(lp).cancel();
991 mReorderAnimators.remove(lp);
992 }
993
Adam Cohen482ed822012-03-02 14:15:13 -0800994 final int oldX = lp.x;
995 final int oldY = lp.y;
996 if (adjustOccupied) {
997 occupied[lp.cellX][lp.cellY] = false;
998 occupied[cellX][cellY] = true;
999 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001000 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001001 if (permanent) {
1002 lp.cellX = info.cellX = cellX;
1003 lp.cellY = info.cellY = cellY;
1004 } else {
1005 lp.tmpCellX = cellX;
1006 lp.tmpCellY = cellY;
1007 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001008 clc.setupLp(lp);
1009 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001010 final int newX = lp.x;
1011 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001012
Adam Cohen76fc0852011-06-17 13:26:23 -07001013 lp.x = oldX;
1014 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001015
Adam Cohen482ed822012-03-02 14:15:13 -08001016 // Exit early if we're not actually moving the view
1017 if (oldX == newX && oldY == newY) {
1018 lp.isLockedToGrid = true;
1019 return true;
1020 }
1021
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001022 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001023 va.setDuration(duration);
1024 mReorderAnimators.put(lp, va);
1025
1026 va.addUpdateListener(new AnimatorUpdateListener() {
1027 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001028 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001029 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001030 lp.x = (int) ((1 - r) * oldX + r * newX);
1031 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001032 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001033 }
1034 });
Adam Cohen482ed822012-03-02 14:15:13 -08001035 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001036 boolean cancelled = false;
1037 public void onAnimationEnd(Animator animation) {
1038 // If the animation was cancelled, it means that another animation
1039 // has interrupted this one, and we don't want to lock the item into
1040 // place just yet.
1041 if (!cancelled) {
1042 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001043 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001044 }
1045 if (mReorderAnimators.containsKey(lp)) {
1046 mReorderAnimators.remove(lp);
1047 }
1048 }
1049 public void onAnimationCancel(Animator animation) {
1050 cancelled = true;
1051 }
1052 });
Adam Cohen482ed822012-03-02 14:15:13 -08001053 va.setStartDelay(delay);
1054 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001055 return true;
1056 }
1057 return false;
1058 }
1059
Tony Wickhama501d492015-11-03 18:05:01 -08001060 void visualizeDropLocation(View v, Bitmap dragOutline, int cellX, int cellY, int spanX,
1061 int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001062 final int oldDragCellX = mDragCell[0];
1063 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001064
Adam Cohen2801caf2011-05-13 20:57:39 -07001065 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001066 return;
1067 }
1068
Adam Cohen482ed822012-03-02 14:15:13 -08001069 if (cellX != oldDragCellX || cellY != oldDragCellY) {
Sunny Goyale78e3d72015-09-24 11:23:31 -07001070 Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
1071 Rect dragRegion = dragObject.dragView.getDragRegion();
1072
Adam Cohen482ed822012-03-02 14:15:13 -08001073 mDragCell[0] = cellX;
1074 mDragCell[1] = cellY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001075
Joe Onorato4be866d2010-10-10 11:26:02 -07001076 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001077 mDragOutlineAnims[oldIndex].animateOut();
1078 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001079 Rect r = mDragOutlines[mDragOutlineCurrent];
Sunny Goyal106bf642015-07-16 12:18:06 -07001080
Adam Cohend41fbf52012-02-16 23:53:59 -08001081 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001082 cellToRect(cellX, cellY, spanX, spanY, r);
Sunny Goyal106bf642015-07-16 12:18:06 -07001083 } else {
1084 // Find the top left corner of the rect the object will occupy
1085 final int[] topLeft = mTmpPoint;
1086 cellToPoint(cellX, cellY, topLeft);
1087
1088 int left = topLeft[0];
1089 int top = topLeft[1];
1090
1091 if (v != null && dragOffset == null) {
1092 // When drawing the drag outline, it did not account for margin offsets
1093 // added by the view's parent.
1094 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1095 left += lp.leftMargin;
1096 top += lp.topMargin;
1097
1098 // Offsets due to the size difference between the View and the dragOutline.
1099 // There is a size difference to account for the outer blur, which may lie
1100 // outside the bounds of the view.
1101 top += (v.getHeight() - dragOutline.getHeight()) / 2;
1102 // We center about the x axis
1103 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1104 - dragOutline.getWidth()) / 2;
1105 } else {
1106 if (dragOffset != null && dragRegion != null) {
1107 // Center the drag region *horizontally* in the cell and apply a drag
1108 // outline offset
1109 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1110 - dragRegion.width()) / 2;
1111 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1112 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1113 top += dragOffset.y + cellPaddingY;
1114 } else {
1115 // Center the drag outline in the cell
1116 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1117 - dragOutline.getWidth()) / 2;
1118 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1119 - dragOutline.getHeight()) / 2;
1120 }
1121 }
1122 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
Adam Cohend41fbf52012-02-16 23:53:59 -08001123 }
Winson Chung150fbab2010-09-29 17:14:26 -07001124
Sunny Goyal106bf642015-07-16 12:18:06 -07001125 Utilities.scaleRectAboutCenter(r, getChildrenScale());
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001126 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1127 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001128
1129 if (dragObject.stateAnnouncer != null) {
1130 String msg;
1131 if (isHotseat()) {
1132 msg = getContext().getString(R.string.move_to_hotseat_position,
1133 Math.max(cellX, cellY) + 1);
1134 } else {
1135 msg = getContext().getString(R.string.move_to_empty_cell,
1136 cellY + 1, cellX + 1);
1137 }
1138 dragObject.stateAnnouncer.announce(msg);
1139 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001140 }
1141 }
1142
Adam Cohene0310962011-04-18 16:15:31 -07001143 public void clearDragOutlines() {
1144 final int oldIndex = mDragOutlineCurrent;
1145 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001146 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001147 }
1148
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001149 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001150 * Find a vacant area that will fit the given bounds nearest the requested
1151 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001152 *
Romain Guy51afc022009-05-04 18:03:43 -07001153 * @param pixelX The X location at which you want to search for a vacant area.
1154 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001155 * @param minSpanX The minimum horizontal span required
1156 * @param minSpanY The minimum vertical span required
1157 * @param spanX Horizontal span of the object.
1158 * @param spanY Vertical span of the object.
1159 * @param result Array in which to place the result, or null (in which case a new array will
1160 * be allocated)
1161 * @return The X, Y cell of a vacant area that can contain this object,
1162 * nearest the requested location.
1163 */
1164 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1165 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001166 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001167 result, resultSpan);
1168 }
1169
Adam Cohend41fbf52012-02-16 23:53:59 -08001170 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1171 private void lazyInitTempRectStack() {
1172 if (mTempRectStack.isEmpty()) {
1173 for (int i = 0; i < mCountX * mCountY; i++) {
1174 mTempRectStack.push(new Rect());
1175 }
1176 }
1177 }
Adam Cohen482ed822012-03-02 14:15:13 -08001178
Adam Cohend41fbf52012-02-16 23:53:59 -08001179 private void recycleTempRects(Stack<Rect> used) {
1180 while (!used.isEmpty()) {
1181 mTempRectStack.push(used.pop());
1182 }
1183 }
1184
1185 /**
1186 * Find a vacant area that will fit the given bounds nearest the requested
1187 * cell location. Uses Euclidean distance to score multiple vacant areas.
1188 *
1189 * @param pixelX The X location at which you want to search for a vacant area.
1190 * @param pixelY The Y location at which you want to search for a vacant area.
1191 * @param minSpanX The minimum horizontal span required
1192 * @param minSpanY The minimum vertical span required
1193 * @param spanX Horizontal span of the object.
1194 * @param spanY Vertical span of the object.
1195 * @param ignoreOccupied If true, the result can be an occupied cell
1196 * @param result Array in which to place the result, or null (in which case a new array will
1197 * be allocated)
1198 * @return The X, Y cell of a vacant area that can contain this object,
1199 * nearest the requested location.
1200 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001201 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1202 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001203 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001204
Adam Cohene3e27a82011-04-15 12:07:39 -07001205 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1206 // to the center of the item, but we are searching based on the top-left cell, so
1207 // we translate the point over to correspond to the top-left.
1208 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1209 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1210
Jeff Sharkey70864282009-04-07 21:08:40 -07001211 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001212 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001213 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001214 final Rect bestRect = new Rect(-1, -1, -1, -1);
1215 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001216
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001217 final int countX = mCountX;
1218 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001219
Adam Cohend41fbf52012-02-16 23:53:59 -08001220 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1221 spanX < minSpanX || spanY < minSpanY) {
1222 return bestXY;
1223 }
1224
1225 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001226 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001227 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1228 int ySize = -1;
1229 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001230 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001231 // First, let's see if this thing fits anywhere
1232 for (int i = 0; i < minSpanX; i++) {
1233 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001234 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001235 continue inner;
1236 }
Michael Jurkac28de512010-08-13 11:27:44 -07001237 }
1238 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001239 xSize = minSpanX;
1240 ySize = minSpanY;
1241
1242 // We know that the item will fit at _some_ acceptable size, now let's see
1243 // how big we can make it. We'll alternate between incrementing x and y spans
1244 // until we hit a limit.
1245 boolean incX = true;
1246 boolean hitMaxX = xSize >= spanX;
1247 boolean hitMaxY = ySize >= spanY;
1248 while (!(hitMaxX && hitMaxY)) {
1249 if (incX && !hitMaxX) {
1250 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001251 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001252 // We can't move out horizontally
1253 hitMaxX = true;
1254 }
1255 }
1256 if (!hitMaxX) {
1257 xSize++;
1258 }
1259 } else if (!hitMaxY) {
1260 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001261 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001262 // We can't move out vertically
1263 hitMaxY = true;
1264 }
1265 }
1266 if (!hitMaxY) {
1267 ySize++;
1268 }
1269 }
1270 hitMaxX |= xSize >= spanX;
1271 hitMaxY |= ySize >= spanY;
1272 incX = !incX;
1273 }
1274 incX = true;
1275 hitMaxX = xSize >= spanX;
1276 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001277 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001278 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001279 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001280
Adam Cohend41fbf52012-02-16 23:53:59 -08001281 // We verify that the current rect is not a sub-rect of any of our previous
1282 // candidates. In this case, the current rect is disqualified in favour of the
1283 // containing rect.
1284 Rect currentRect = mTempRectStack.pop();
1285 currentRect.set(x, y, x + xSize, y + ySize);
1286 boolean contained = false;
1287 for (Rect r : validRegions) {
1288 if (r.contains(currentRect)) {
1289 contained = true;
1290 break;
1291 }
1292 }
1293 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001294 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001295
Adam Cohend41fbf52012-02-16 23:53:59 -08001296 if ((distance <= bestDistance && !contained) ||
1297 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001298 bestDistance = distance;
1299 bestXY[0] = x;
1300 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001301 if (resultSpan != null) {
1302 resultSpan[0] = xSize;
1303 resultSpan[1] = ySize;
1304 }
1305 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001306 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001307 }
1308 }
1309
Adam Cohenc0dcf592011-06-01 15:30:43 -07001310 // Return -1, -1 if no suitable location found
1311 if (bestDistance == Double.MAX_VALUE) {
1312 bestXY[0] = -1;
1313 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001314 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001315 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001316 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001317 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001318
Adam Cohen482ed822012-03-02 14:15:13 -08001319 /**
1320 * Find a vacant area that will fit the given bounds nearest the requested
1321 * cell location, and will also weigh in a suggested direction vector of the
1322 * desired location. This method computers distance based on unit grid distances,
1323 * not pixel distances.
1324 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001325 * @param cellX The X cell nearest to which you want to search for a vacant area.
1326 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001327 * @param spanX Horizontal span of the object.
1328 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001329 * @param direction The favored direction in which the views should move from x, y
Sunny Goyal9eba1fd2015-10-16 08:58:57 -07001330 * @param occupied The array which represents which cells in the CellLayout are occupied
Adam Cohen47a876d2012-03-19 13:21:41 -07001331 * @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 Wickham9e0702f2015-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.
Tony Wickham112ac952015-11-12 12:31:50 -08002179 if (!Utilities.isPowerSaverOn(getContext())) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07002180 va.setRepeatMode(ValueAnimator.REVERSE);
2181 va.setRepeatCount(ValueAnimator.INFINITE);
2182 }
2183
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002184 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002185 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002186 va.addUpdateListener(new AnimatorUpdateListener() {
2187 @Override
2188 public void onAnimationUpdate(ValueAnimator animation) {
2189 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002190 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2191 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2192 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002193 child.setTranslationX(x);
2194 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002195 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002196 child.setScaleX(s);
2197 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002198 }
2199 });
2200 va.addListener(new AnimatorListenerAdapter() {
2201 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002202 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002203 initDeltaX = 0;
2204 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002205 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002206 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002207 }
2208 });
Adam Cohen19f37922012-03-21 11:59:11 -07002209 mShakeAnimators.put(child, this);
2210 va.start();
2211 }
2212
Adam Cohend024f982012-05-23 18:26:45 -07002213 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002214 if (a != null) {
2215 a.cancel();
2216 }
Adam Cohen19f37922012-03-21 11:59:11 -07002217 }
Adam Cohene7587d22012-05-24 18:50:02 -07002218
Adam Cohen091440a2015-03-18 14:16:05 -07002219 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002220 if (a != null) {
2221 a.cancel();
2222 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002223
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002224 a = new LauncherViewPropertyAnimator(child)
2225 .scaleX(getChildrenScale())
2226 .scaleY(getChildrenScale())
2227 .translationX(0)
2228 .translationY(0)
2229 .setDuration(REORDER_ANIMATION_DURATION);
2230 a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2231 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002232 }
Adam Cohen19f37922012-03-21 11:59:11 -07002233 }
2234
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002235 private void completeAndClearReorderPreviewAnimations() {
2236 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002237 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002238 }
2239 mShakeAnimators.clear();
2240 }
2241
Adam Cohen482ed822012-03-02 14:15:13 -08002242 private void commitTempPlacement() {
2243 for (int i = 0; i < mCountX; i++) {
2244 for (int j = 0; j < mCountY; j++) {
2245 mOccupied[i][j] = mTmpOccupied[i][j];
2246 }
2247 }
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002248
2249 long screenId = mLauncher.getWorkspace().getIdForScreen(this);
2250 int container = Favorites.CONTAINER_DESKTOP;
2251
2252 if (mLauncher.isHotseatLayout(this)) {
2253 screenId = -1;
2254 container = Favorites.CONTAINER_HOTSEAT;
2255 }
2256
Michael Jurkaa52570f2012-03-20 03:18:20 -07002257 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002258 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002259 View child = mShortcutsAndWidgets.getChildAt(i);
2260 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2261 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002262 // We do a null check here because the item info can be null in the case of the
2263 // AllApps button in the hotseat.
2264 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002265 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2266 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2267 || info.spanY != lp.cellVSpan);
2268
Adam Cohen2acce882012-03-28 19:03:19 -07002269 info.cellX = lp.cellX = lp.tmpCellX;
2270 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002271 info.spanX = lp.cellHSpan;
2272 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002273
2274 if (requiresDbUpdate) {
2275 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
2276 info.cellX, info.cellY, info.spanX, info.spanY);
2277 }
Adam Cohen2acce882012-03-28 19:03:19 -07002278 }
Adam Cohen482ed822012-03-02 14:15:13 -08002279 }
2280 }
2281
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002282 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002283 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002284 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002285 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002286 lp.useTmpCoords = useTempCoords;
2287 }
2288 }
2289
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002290 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002291 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2292 int[] result = new int[2];
2293 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002294 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002295 resultSpan);
2296 if (result[0] >= 0 && result[1] >= 0) {
2297 copyCurrentStateToSolution(solution, false);
2298 solution.dragViewX = result[0];
2299 solution.dragViewY = result[1];
2300 solution.dragViewSpanX = resultSpan[0];
2301 solution.dragViewSpanY = resultSpan[1];
2302 solution.isSolution = true;
2303 } else {
2304 solution.isSolution = false;
2305 }
2306 return solution;
2307 }
2308
2309 public void prepareChildForDrag(View child) {
2310 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002311 }
2312
Adam Cohen19f37922012-03-21 11:59:11 -07002313 /* This seems like it should be obvious and straight-forward, but when the direction vector
2314 needs to match with the notion of the dragView pushing other views, we have to employ
2315 a slightly more subtle notion of the direction vector. The question is what two points is
2316 the vector between? The center of the dragView and its desired destination? Not quite, as
2317 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2318 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2319 or right, which helps make pushing feel right.
2320 */
2321 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2322 int spanY, View dragView, int[] resultDirection) {
2323 int[] targetDestination = new int[2];
2324
2325 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2326 Rect dragRect = new Rect();
2327 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2328 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2329
2330 Rect dropRegionRect = new Rect();
2331 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2332 dragView, dropRegionRect, mIntersectingViews);
2333
2334 int dropRegionSpanX = dropRegionRect.width();
2335 int dropRegionSpanY = dropRegionRect.height();
2336
2337 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2338 dropRegionRect.height(), dropRegionRect);
2339
2340 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2341 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2342
2343 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2344 deltaX = 0;
2345 }
2346 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2347 deltaY = 0;
2348 }
2349
2350 if (deltaX == 0 && deltaY == 0) {
2351 // No idea what to do, give a random direction.
2352 resultDirection[0] = 1;
2353 resultDirection[1] = 0;
2354 } else {
2355 computeDirectionVector(deltaX, deltaY, resultDirection);
2356 }
2357 }
2358
2359 // For a given cell and span, fetch the set of views intersecting the region.
2360 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2361 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2362 if (boundingRect != null) {
2363 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2364 }
2365 intersectingViews.clear();
2366 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2367 Rect r1 = new Rect();
2368 final int count = mShortcutsAndWidgets.getChildCount();
2369 for (int i = 0; i < count; i++) {
2370 View child = mShortcutsAndWidgets.getChildAt(i);
2371 if (child == dragView) continue;
2372 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2373 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2374 if (Rect.intersects(r0, r1)) {
2375 mIntersectingViews.add(child);
2376 if (boundingRect != null) {
2377 boundingRect.union(r1);
2378 }
2379 }
2380 }
2381 }
2382
2383 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2384 View dragView, int[] result) {
2385 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2386 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2387 mIntersectingViews);
2388 return !mIntersectingViews.isEmpty();
2389 }
2390
2391 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002392 completeAndClearReorderPreviewAnimations();
2393 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2394 final int count = mShortcutsAndWidgets.getChildCount();
2395 for (int i = 0; i < count; i++) {
2396 View child = mShortcutsAndWidgets.getChildAt(i);
2397 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2398 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2399 lp.tmpCellX = lp.cellX;
2400 lp.tmpCellY = lp.cellY;
2401 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2402 0, false, false);
2403 }
Adam Cohen19f37922012-03-21 11:59:11 -07002404 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002405 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002406 }
Adam Cohen19f37922012-03-21 11:59:11 -07002407 }
2408
Adam Cohenbebf0422012-04-11 18:06:28 -07002409 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2410 View dragView, int[] direction, boolean commit) {
2411 int[] pixelXY = new int[2];
2412 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2413
2414 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002415 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002416 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2417
2418 setUseTempCoords(true);
2419 if (swapSolution != null && swapSolution.isSolution) {
2420 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2421 // committing anything or animating anything as we just want to determine if a solution
2422 // exists
2423 copySolutionToTempState(swapSolution, dragView);
2424 setItemPlacementDirty(true);
2425 animateItemsToSolution(swapSolution, dragView, commit);
2426
2427 if (commit) {
2428 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002429 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002430 setItemPlacementDirty(false);
2431 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002432 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2433 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002434 }
2435 mShortcutsAndWidgets.requestLayout();
2436 }
2437 return swapSolution.isSolution;
2438 }
2439
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002440 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002441 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002442 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002443 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002444
2445 if (resultSpan == null) {
2446 resultSpan = new int[2];
2447 }
2448
Adam Cohen19f37922012-03-21 11:59:11 -07002449 // When we are checking drop validity or actually dropping, we don't recompute the
2450 // direction vector, since we want the solution to match the preview, and it's possible
2451 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002452 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2453 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002454 mDirectionVector[0] = mPreviousReorderDirection[0];
2455 mDirectionVector[1] = mPreviousReorderDirection[1];
2456 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002457 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2458 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2459 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002460 }
2461 } else {
2462 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2463 mPreviousReorderDirection[0] = mDirectionVector[0];
2464 mPreviousReorderDirection[1] = mDirectionVector[1];
2465 }
2466
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002467 // Find a solution involving pushing / displacing any items in the way
2468 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002469 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2470
2471 // We attempt the approach which doesn't shuffle views at all
2472 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2473 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2474
2475 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002476
2477 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2478 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002479 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2480 finalSolution = swapSolution;
2481 } else if (noShuffleSolution.isSolution) {
2482 finalSolution = noShuffleSolution;
2483 }
2484
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002485 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002486 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002487 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2488 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002489 result[0] = finalSolution.dragViewX;
2490 result[1] = finalSolution.dragViewY;
2491 resultSpan[0] = finalSolution.dragViewSpanX;
2492 resultSpan[1] = finalSolution.dragViewSpanY;
2493 } else {
2494 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2495 }
2496 return result;
2497 }
2498
Adam Cohen482ed822012-03-02 14:15:13 -08002499 boolean foundSolution = true;
2500 if (!DESTRUCTIVE_REORDER) {
2501 setUseTempCoords(true);
2502 }
2503
2504 if (finalSolution != null) {
2505 result[0] = finalSolution.dragViewX;
2506 result[1] = finalSolution.dragViewY;
2507 resultSpan[0] = finalSolution.dragViewSpanX;
2508 resultSpan[1] = finalSolution.dragViewSpanY;
2509
2510 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2511 // committing anything or animating anything as we just want to determine if a solution
2512 // exists
2513 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2514 if (!DESTRUCTIVE_REORDER) {
2515 copySolutionToTempState(finalSolution, dragView);
2516 }
2517 setItemPlacementDirty(true);
2518 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2519
Adam Cohen19f37922012-03-21 11:59:11 -07002520 if (!DESTRUCTIVE_REORDER &&
2521 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002522 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002523 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002524 setItemPlacementDirty(false);
2525 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002526 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2527 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002528 }
2529 }
2530 } else {
2531 foundSolution = false;
2532 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2533 }
2534
2535 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2536 setUseTempCoords(false);
2537 }
Adam Cohen482ed822012-03-02 14:15:13 -08002538
Michael Jurkaa52570f2012-03-20 03:18:20 -07002539 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002540 return result;
2541 }
2542
Adam Cohen19f37922012-03-21 11:59:11 -07002543 void setItemPlacementDirty(boolean dirty) {
2544 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002545 }
Adam Cohen19f37922012-03-21 11:59:11 -07002546 boolean isItemPlacementDirty() {
2547 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002548 }
2549
Adam Cohen091440a2015-03-18 14:16:05 -07002550 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002551 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002552 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2553 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002554 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002555 boolean isSolution = false;
2556 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2557
Adam Cohenf3900c22012-11-16 18:28:11 -08002558 void save() {
2559 // Copy current state into savedMap
2560 for (View v: map.keySet()) {
2561 map.get(v).copy(savedMap.get(v));
2562 }
2563 }
2564
2565 void restore() {
2566 // Restore current state from savedMap
2567 for (View v: savedMap.keySet()) {
2568 savedMap.get(v).copy(map.get(v));
2569 }
2570 }
2571
2572 void add(View v, CellAndSpan cs) {
2573 map.put(v, cs);
2574 savedMap.put(v, new CellAndSpan());
2575 sortedViews.add(v);
2576 }
2577
Adam Cohen482ed822012-03-02 14:15:13 -08002578 int area() {
2579 return dragViewSpanX * dragViewSpanY;
2580 }
Adam Cohen8baab352012-03-20 17:39:21 -07002581 }
2582
2583 private class CellAndSpan {
2584 int x, y;
2585 int spanX, spanY;
2586
Adam Cohenf3900c22012-11-16 18:28:11 -08002587 public CellAndSpan() {
2588 }
2589
2590 public void copy(CellAndSpan copy) {
2591 copy.x = x;
2592 copy.y = y;
2593 copy.spanX = spanX;
2594 copy.spanY = spanY;
2595 }
2596
Adam Cohen8baab352012-03-20 17:39:21 -07002597 public CellAndSpan(int x, int y, int spanX, int spanY) {
2598 this.x = x;
2599 this.y = y;
2600 this.spanX = spanX;
2601 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002602 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002603
2604 public String toString() {
2605 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2606 }
2607
Adam Cohen482ed822012-03-02 14:15:13 -08002608 }
2609
Adam Cohendf035382011-04-11 17:22:04 -07002610 /**
Adam Cohendf035382011-04-11 17:22:04 -07002611 * Find a starting cell position that will fit the given bounds nearest the requested
2612 * cell location. Uses Euclidean distance to score multiple vacant areas.
2613 *
2614 * @param pixelX The X location at which you want to search for a vacant area.
2615 * @param pixelY The Y location at which you want to search for a vacant area.
2616 * @param spanX Horizontal span of the object.
2617 * @param spanY Vertical span of the object.
2618 * @param ignoreView Considers space occupied by this view as unoccupied
2619 * @param result Previously returned value to possibly recycle.
2620 * @return The X, Y cell of a vacant area that can contain this object,
2621 * nearest the requested location.
2622 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002623 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2624 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002625 }
2626
Michael Jurka0280c3b2010-09-17 15:00:07 -07002627 boolean existsEmptyCell() {
2628 return findCellForSpan(null, 1, 1);
2629 }
2630
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002631 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002632 * Finds the upper-left coordinate of the first rectangle in the grid that can
2633 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2634 * then this method will only return coordinates for rectangles that contain the cell
2635 * (intersectX, intersectY)
2636 *
2637 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2638 * can be found.
2639 * @param spanX The horizontal span of the cell we want to find.
2640 * @param spanY The vertical span of the cell we want to find.
2641 *
2642 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002643 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002644 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002645 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002646 final int endX = mCountX - (spanX - 1);
2647 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002648
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002649 for (int y = 0; y < endY && !foundCell; y++) {
2650 inner:
2651 for (int x = 0; x < endX; x++) {
2652 for (int i = 0; i < spanX; i++) {
2653 for (int j = 0; j < spanY; j++) {
2654 if (mOccupied[x + i][y + j]) {
2655 // small optimization: we can skip to after the column we just found
2656 // an occupied cell
2657 x += i;
2658 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002659 }
2660 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002661 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002662 if (cellXY != null) {
2663 cellXY[0] = x;
2664 cellXY[1] = y;
2665 }
2666 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002667 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002668 }
2669 }
2670
Michael Jurka28750fb2010-09-24 17:43:49 -07002671 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002672 }
2673
2674 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002675 * A drag event has begun over this layout.
2676 * It may have begun over this layout (in which case onDragChild is called first),
2677 * or it may have begun on another layout.
2678 */
2679 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002680 mDragging = true;
2681 }
2682
2683 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002684 * Called when drag has left this CellLayout or has been completed (successfully or not)
2685 */
2686 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002687 // This can actually be called when we aren't in a drag, e.g. when adding a new
2688 // item to this layout via the customize drawer.
2689 // Guard against that case.
2690 if (mDragging) {
2691 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002692 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002693
2694 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002695 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002696 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2697 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002698 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002699 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002700 }
2701
2702 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002703 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002704 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002705 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002706 *
2707 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002709 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002710 if (child != null) {
2711 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002712 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002713 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002714 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002715 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002716 }
2717
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002718 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002719 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002720 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002721 * @param cellX X coordinate of upper left corner expressed as a cell position
2722 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002723 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002724 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002725 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002726 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002727 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002728 final int cellWidth = mCellWidth;
2729 final int cellHeight = mCellHeight;
2730 final int widthGap = mWidthGap;
2731 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002732
Winson Chung4b825dcd2011-06-19 12:41:22 -07002733 final int hStartPadding = getPaddingLeft();
2734 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002735
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002736 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2737 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2738
2739 int x = hStartPadding + cellX * (cellWidth + widthGap);
2740 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002741
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002742 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002743 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002744
Michael Jurka0280c3b2010-09-17 15:00:07 -07002745 private void clearOccupiedCells() {
2746 for (int x = 0; x < mCountX; x++) {
2747 for (int y = 0; y < mCountY; y++) {
2748 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002749 }
2750 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002751 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002752
Adam Cohend4844c32011-02-18 19:25:06 -08002753 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002754 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002755 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002756 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002757 }
2758
Adam Cohend4844c32011-02-18 19:25:06 -08002759 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002760 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002761 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002762 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002763 }
2764
Adam Cohen482ed822012-03-02 14:15:13 -08002765 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2766 boolean value) {
2767 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002768 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2769 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002770 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002771 }
2772 }
2773 }
2774
Adam Cohen2801caf2011-05-13 20:57:39 -07002775 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002776 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002777 (Math.max((mCountX - 1), 0) * mWidthGap);
2778 }
2779
2780 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002781 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002782 (Math.max((mCountY - 1), 0) * mHeightGap);
2783 }
2784
Michael Jurka66d72172011-04-12 16:29:25 -07002785 public boolean isOccupied(int x, int y) {
2786 if (x < mCountX && y < mCountY) {
2787 return mOccupied[x][y];
2788 } else {
2789 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2790 }
2791 }
2792
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002793 @Override
2794 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2795 return new CellLayout.LayoutParams(getContext(), attrs);
2796 }
2797
2798 @Override
2799 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2800 return p instanceof CellLayout.LayoutParams;
2801 }
2802
2803 @Override
2804 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2805 return new CellLayout.LayoutParams(p);
2806 }
2807
2808 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2809 /**
2810 * Horizontal location of the item in the grid.
2811 */
2812 @ViewDebug.ExportedProperty
2813 public int cellX;
2814
2815 /**
2816 * Vertical location of the item in the grid.
2817 */
2818 @ViewDebug.ExportedProperty
2819 public int cellY;
2820
2821 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002822 * Temporary horizontal location of the item in the grid during reorder
2823 */
2824 public int tmpCellX;
2825
2826 /**
2827 * Temporary vertical location of the item in the grid during reorder
2828 */
2829 public int tmpCellY;
2830
2831 /**
2832 * Indicates that the temporary coordinates should be used to layout the items
2833 */
2834 public boolean useTmpCoords;
2835
2836 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002837 * Number of cells spanned horizontally by the item.
2838 */
2839 @ViewDebug.ExportedProperty
2840 public int cellHSpan;
2841
2842 /**
2843 * Number of cells spanned vertically by the item.
2844 */
2845 @ViewDebug.ExportedProperty
2846 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002847
Adam Cohen1b607ed2011-03-03 17:26:50 -08002848 /**
2849 * Indicates whether the item will set its x, y, width and height parameters freely,
2850 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2851 */
Adam Cohend4844c32011-02-18 19:25:06 -08002852 public boolean isLockedToGrid = true;
2853
Adam Cohen482ed822012-03-02 14:15:13 -08002854 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002855 * Indicates that this item should use the full extents of its parent.
2856 */
2857 public boolean isFullscreen = false;
2858
2859 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002860 * Indicates whether this item can be reordered. Always true except in the case of the
2861 * the AllApps button.
2862 */
2863 public boolean canReorder = true;
2864
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002865 // X coordinate of the view in the layout.
2866 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002867 public int x;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002868 // Y coordinate of the view in the layout.
2869 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002870 public int y;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002871
Romain Guy84f296c2009-11-04 15:00:44 -08002872 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002873
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002874 public LayoutParams(Context c, AttributeSet attrs) {
2875 super(c, attrs);
2876 cellHSpan = 1;
2877 cellVSpan = 1;
2878 }
2879
2880 public LayoutParams(ViewGroup.LayoutParams source) {
2881 super(source);
2882 cellHSpan = 1;
2883 cellVSpan = 1;
2884 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002885
2886 public LayoutParams(LayoutParams source) {
2887 super(source);
2888 this.cellX = source.cellX;
2889 this.cellY = source.cellY;
2890 this.cellHSpan = source.cellHSpan;
2891 this.cellVSpan = source.cellVSpan;
2892 }
2893
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002894 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002895 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002896 this.cellX = cellX;
2897 this.cellY = cellY;
2898 this.cellHSpan = cellHSpan;
2899 this.cellVSpan = cellVSpan;
2900 }
2901
Adam Cohen2374abf2013-04-16 14:56:57 -07002902 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2903 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002904 if (isLockedToGrid) {
2905 final int myCellHSpan = cellHSpan;
2906 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002907 int myCellX = useTmpCoords ? tmpCellX : cellX;
2908 int myCellY = useTmpCoords ? tmpCellY : cellY;
2909
2910 if (invertHorizontally) {
2911 myCellX = colCount - myCellX - cellHSpan;
2912 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002913
Adam Cohend4844c32011-02-18 19:25:06 -08002914 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2915 leftMargin - rightMargin;
2916 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2917 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002918 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2919 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002920 }
2921 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002922
Winson Chungaafa03c2010-06-11 17:34:16 -07002923 public String toString() {
2924 return "(" + this.cellX + ", " + this.cellY + ")";
2925 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002926
2927 public void setWidth(int width) {
2928 this.width = width;
2929 }
2930
2931 public int getWidth() {
2932 return width;
2933 }
2934
2935 public void setHeight(int height) {
2936 this.height = height;
2937 }
2938
2939 public int getHeight() {
2940 return height;
2941 }
2942
2943 public void setX(int x) {
2944 this.x = x;
2945 }
2946
2947 public int getX() {
2948 return x;
2949 }
2950
2951 public void setY(int y) {
2952 this.y = y;
2953 }
2954
2955 public int getY() {
2956 return y;
2957 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002958 }
2959
Michael Jurka0280c3b2010-09-17 15:00:07 -07002960 // This class stores info for two purposes:
2961 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2962 // its spanX, spanY, and the screen it is on
2963 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2964 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2965 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002966 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002967 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002968 int cellX = -1;
2969 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002970 int spanX;
2971 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002972 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002973 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002974
Sunny Goyal83a8f042015-05-19 12:52:12 -07002975 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002976 cell = v;
2977 cellX = info.cellX;
2978 cellY = info.cellY;
2979 spanX = info.spanX;
2980 spanY = info.spanY;
2981 screenId = info.screenId;
2982 container = info.container;
2983 }
2984
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002985 @Override
2986 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002987 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2988 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002989 }
2990 }
Michael Jurkad771c962011-08-09 15:00:48 -07002991
Sunny Goyala9116722015-04-29 13:55:58 -07002992 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2993 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2994 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002995
Tony Wickham86930612015-09-09 13:50:40 -07002996 /**
2997 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
2998 * if necessary).
2999 */
3000 public boolean hasReorderSolution(ItemInfo itemInfo) {
3001 int[] cellPoint = new int[2];
3002 // Check for a solution starting at every cell.
3003 for (int cellX = 0; cellX < getCountX(); cellX++) {
3004 for (int cellY = 0; cellY < getCountY(); cellY++) {
3005 cellToPoint(cellX, cellY, cellPoint);
3006 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
3007 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
3008 true, new ItemConfiguration()).isSolution) {
3009 return true;
3010 }
3011 }
3012 }
3013 return false;
3014 }
3015
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003016 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3017 int x2 = x + spanX - 1;
3018 int y2 = y + spanY - 1;
3019 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3020 return false;
3021 }
3022 for (int i = x; i <= x2; i++) {
3023 for (int j = y; j <= y2; j++) {
3024 if (mOccupied[i][j]) {
3025 return false;
3026 }
3027 }
3028 }
3029
3030 return true;
3031 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003032}