blob: 9370e57b59252b35e6e04ff25f58675f8d896635 [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;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080048
Sunny Goyal4b6eb262015-05-14 19:24:40 -070049import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Sunny Goyalaa8ef112015-06-12 20:04:41 -070050import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070051import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
52import com.android.launcher3.accessibility.FolderAccessibilityHelper;
53import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Tony Wickhame0c33232016-02-08 11:37:04 -080054import com.android.launcher3.config.FeatureFlags;
Sunny Goyal6c56c682015-07-16 14:09:05 -070055import com.android.launcher3.config.ProviderConfig;
Sunny Goyalfc956e52016-02-17 13:24:20 -080056import com.android.launcher3.FolderIcon.FolderRingAnimator;
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
Tony Wickhame0c33232016-02-08 11:37:04 -0800214 mBackground = (TransitionDrawable) res.getDrawable(
215 FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? R.drawable.bg_screenpanel
216 : R.drawable.bg_celllayout);
Sunny Goyal2805e632015-05-20 15:35:32 -0700217 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700218 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800219
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800220 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700221 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700222
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700223 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700224 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700225 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700226 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800227 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700228 }
229
230 // When dragging things around the home screens, we show a green outline of
231 // where the item will land. The outlines gradually fade out, leaving a trail
232 // behind the drag path.
233 // Set up all the animations that are used to implement this fading.
234 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700235 final float fromAlphaValue = 0;
236 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700237
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700238 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700239
240 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700241 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100242 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700243 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700244 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700245 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700246 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700247 final Bitmap outline = (Bitmap)anim.getTag();
248
249 // If an animation is started and then stopped very quickly, we can still
250 // get spurious updates we've cleared the tag. Guard against this.
251 if (outline == null) {
Tony Wickhama0628cc2015-10-14 15:23:04 -0700252 if (LOGD) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700253 Object val = animation.getAnimatedValue();
254 Log.d(TAG, "anim " + thisIndex + " update: " + val +
255 ", isStopped " + anim.isStopped());
256 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700257 // Try to prevent it from continuing to run
258 animation.cancel();
259 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700260 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800261 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700262 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700263 }
264 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700265 // The animation holds a reference to the drag outline bitmap as long is it's
266 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700267 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700268 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700270 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700271 anim.setTag(null);
272 }
273 }
274 });
275 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700276 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700277
Michael Jurkaa52570f2012-03-20 03:18:20 -0700278 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700279 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700280 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700281
Mady Mellorbb835202015-07-15 16:34:34 -0700282 mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
Mady Melloref044dd2015-06-02 15:35:07 -0700283
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700284 mTouchFeedbackView = new ClickShadowView(context);
285 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700286 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700287 }
288
Adam Cohenc9735cf2015-01-23 16:11:55 -0800289 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700290 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800291 mUseTouchHelper = enable;
292 if (!enable) {
293 ViewCompat.setAccessibilityDelegate(this, null);
294 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
295 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
296 setOnClickListener(mLauncher);
297 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700298 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
299 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
300 mTouchHelper = new WorkspaceAccessibilityHelper(this);
301 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
302 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
303 mTouchHelper = new FolderAccessibilityHelper(this);
304 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800305 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
306 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
307 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
308 setOnClickListener(mTouchHelper);
309 }
310
311 // Invalidate the accessibility hierarchy
312 if (getParent() != null) {
313 getParent().notifySubtreeAccessibilityStateChanged(
314 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
315 }
316 }
317
318 @Override
319 public boolean dispatchHoverEvent(MotionEvent event) {
320 // Always attempt to dispatch hover events to accessibility first.
321 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
322 return true;
323 }
324 return super.dispatchHoverEvent(event);
325 }
326
327 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800328 public boolean onInterceptTouchEvent(MotionEvent ev) {
329 if (mUseTouchHelper ||
330 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
331 return true;
332 }
333 return false;
334 }
335
Mady Melloref044dd2015-06-02 15:35:07 -0700336 @Override
337 public boolean onTouchEvent(MotionEvent ev) {
338 boolean handled = super.onTouchEvent(ev);
339 // Stylus button press on a home screen should not switch between overview mode and
340 // the home screen mode, however, once in overview mode stylus button press should be
341 // enabled to allow rearranging the different home screens. So check what mode
342 // the workspace is in, and only perform stylus button presses while in overview mode.
343 if (mLauncher.mWorkspace.isInOverviewMode()
Mady Mellorbb835202015-07-15 16:34:34 -0700344 && mStylusEventHelper.onMotionEvent(ev)) {
Mady Melloref044dd2015-06-02 15:35:07 -0700345 return true;
346 }
347 return handled;
348 }
349
Chris Craik01f2d7f2013-10-01 14:41:56 -0700350 public void enableHardwareLayer(boolean hasLayer) {
351 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700352 }
353
354 public void buildHardwareLayer() {
355 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700356 }
357
Adam Cohen307fe232012-08-16 17:55:58 -0700358 public float getChildrenScale() {
359 return mIsHotseat ? mHotseatScale : 1.0f;
360 }
361
Winson Chung5f8afe62013-08-12 16:19:28 -0700362 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700363 mFixedCellWidth = mCellWidth = width;
364 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700365 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
366 mCountX, mCountY);
367 }
368
Adam Cohen2801caf2011-05-13 20:57:39 -0700369 public void setGridSize(int x, int y) {
370 mCountX = x;
371 mCountY = y;
372 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800373 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700374 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700375 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700376 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700377 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700378 }
379
Adam Cohen2374abf2013-04-16 14:56:57 -0700380 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
381 public void setInvertIfRtl(boolean invert) {
382 mShortcutsAndWidgets.setInvertIfRtl(invert);
383 }
384
Adam Cohen917e3882013-10-31 15:03:35 -0700385 public void setDropPending(boolean pending) {
386 mDropPending = pending;
387 }
388
389 public boolean isDropPending() {
390 return mDropPending;
391 }
392
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700393 @Override
394 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700395 if (icon == null || background == null) {
396 mTouchFeedbackView.setBitmap(null);
397 mTouchFeedbackView.animate().cancel();
398 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700399 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700400 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
401 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700402 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800403 }
404 }
405
Adam Cohenc50438c2014-08-19 17:43:05 -0700406 void disableDragTarget() {
407 mIsDragTarget = false;
408 }
409
Tony Wickham0f97b782015-12-02 17:55:07 -0800410 public boolean isDragTarget() {
411 return mIsDragTarget;
412 }
413
Adam Cohenc50438c2014-08-19 17:43:05 -0700414 void setIsDragOverlapping(boolean isDragOverlapping) {
415 if (mIsDragOverlapping != isDragOverlapping) {
416 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700417 if (mIsDragOverlapping) {
418 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
419 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700420 if (mBackgroundAlpha > 0f) {
421 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
422 } else {
423 mBackground.resetTransition();
424 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700425 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700426 invalidate();
427 }
428 }
429
Sunny Goyale2fd14b2015-08-27 17:45:46 -0700430 public void disableJailContent() {
431 mJailContent = false;
432 }
433
434 @Override
435 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
436 if (mJailContent) {
437 ParcelableSparseArray jail = getJailedArray(container);
438 super.dispatchSaveInstanceState(jail);
439 container.put(R.id.cell_layout_jail_id, jail);
440 } else {
441 super.dispatchSaveInstanceState(container);
442 }
443 }
444
445 @Override
446 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
447 super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
448 }
449
450 private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
451 final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
452 return parcelable instanceof ParcelableSparseArray ?
453 (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
454 }
455
Tony Wickham0f97b782015-12-02 17:55:07 -0800456 public boolean getIsDragOverlapping() {
457 return mIsDragOverlapping;
458 }
459
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700460 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700461 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700462 if (!mIsDragTarget) {
463 return;
464 }
465
Michael Jurka3e7c7632010-10-02 16:01:03 -0700466 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
467 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
468 // When we're small, we are either drawn normally or in the "accepts drops" state (during
469 // a drag). However, we also drag the mini hover background *over* one of those two
470 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700471 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700472 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700473 }
Romain Guya6abce82009-11-10 02:54:41 -0800474
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700475 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700476 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700477 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700478 if (alpha > 0) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700479 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700480 paint.setAlpha((int)(alpha + .5f));
Sunny Goyal106bf642015-07-16 12:18:06 -0700481 canvas.drawBitmap(b, null, mDragOutlines[i], paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700482 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700483 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800484
Adam Cohen482ed822012-03-02 14:15:13 -0800485 if (DEBUG_VISUALIZE_OCCUPIED) {
486 int[] pt = new int[2];
487 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700488 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800489 for (int i = 0; i < mCountX; i++) {
490 for (int j = 0; j < mCountY; j++) {
491 if (mOccupied[i][j]) {
492 cellToPoint(i, j, pt);
493 canvas.save();
494 canvas.translate(pt[0], pt[1]);
495 cd.draw(canvas);
496 canvas.restore();
497 }
498 }
499 }
500 }
501
Andrew Flynn850d2e72012-04-26 16:51:20 -0700502 int previewOffset = FolderRingAnimator.sPreviewSize;
503
Adam Cohen69ce2e52011-07-03 19:25:21 -0700504 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700505 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700506 for (int i = 0; i < mFolderOuterRings.size(); i++) {
507 FolderRingAnimator fra = mFolderOuterRings.get(i);
508
Adam Cohen5108bc02013-09-20 17:04:51 -0700509 Drawable d;
510 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700511 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700512 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700513
Winson Chung89f97052013-09-20 11:32:26 -0700514 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700515 int centerX = mTempLocation[0] + mCellWidth / 2;
516 int centerY = mTempLocation[1] + previewOffset / 2 +
517 child.getPaddingTop() + grid.folderBackgroundOffset;
518
Adam Cohen5108bc02013-09-20 17:04:51 -0700519 // Draw outer ring, if it exists
520 if (FolderIcon.HAS_OUTER_RING) {
521 d = FolderRingAnimator.sSharedOuterRingDrawable;
522 width = (int) (fra.getOuterRingSize() * getChildrenScale());
523 height = width;
524 canvas.save();
525 canvas.translate(centerX - width / 2, centerY - height / 2);
526 d.setBounds(0, 0, width, height);
527 d.draw(canvas);
528 canvas.restore();
529 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700530
Winson Chung89f97052013-09-20 11:32:26 -0700531 // Draw inner ring
532 d = FolderRingAnimator.sSharedInnerRingDrawable;
533 width = (int) (fra.getInnerRingSize() * getChildrenScale());
534 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700535 canvas.save();
536 canvas.translate(centerX - width / 2, centerY - width / 2);
537 d.setBounds(0, 0, width, height);
538 d.draw(canvas);
539 canvas.restore();
540 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700541 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700542
543 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
544 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
545 int width = d.getIntrinsicWidth();
546 int height = d.getIntrinsicHeight();
547
548 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700549 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700550 if (child != null) {
551 int centerX = mTempLocation[0] + mCellWidth / 2;
552 int centerY = mTempLocation[1] + previewOffset / 2 +
553 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700554
Winson Chung89f97052013-09-20 11:32:26 -0700555 canvas.save();
556 canvas.translate(centerX - width / 2, centerY - width / 2);
557 d.setBounds(0, 0, width, height);
558 d.draw(canvas);
559 canvas.restore();
560 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700561 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700562 }
563
564 public void showFolderAccept(FolderRingAnimator fra) {
565 mFolderOuterRings.add(fra);
566 }
567
568 public void hideFolderAccept(FolderRingAnimator fra) {
569 if (mFolderOuterRings.contains(fra)) {
570 mFolderOuterRings.remove(fra);
571 }
572 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700573 }
574
Adam Cohenc51934b2011-07-26 21:07:43 -0700575 public void setFolderLeaveBehindCell(int x, int y) {
576 mFolderLeaveBehindCell[0] = x;
577 mFolderLeaveBehindCell[1] = y;
578 invalidate();
579 }
580
581 public void clearFolderLeaveBehind() {
582 mFolderLeaveBehindCell[0] = -1;
583 mFolderLeaveBehindCell[1] = -1;
584 invalidate();
585 }
586
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700587 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700588 public boolean shouldDelayChildPressedState() {
589 return false;
590 }
591
Adam Cohen1462de32012-07-24 22:34:36 -0700592 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700593 try {
594 dispatchRestoreInstanceState(states);
595 } catch (IllegalArgumentException ex) {
Sunny Goyal6c56c682015-07-16 14:09:05 -0700596 if (ProviderConfig.IS_DOGFOOD_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700597 throw ex;
598 }
599 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
600 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
601 }
Adam Cohen1462de32012-07-24 22:34:36 -0700602 }
603
Michael Jurkae6235dd2011-10-04 15:02:05 -0700604 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700605 public void cancelLongPress() {
606 super.cancelLongPress();
607
608 // Cancel long press for all children
609 final int count = getChildCount();
610 for (int i = 0; i < count; i++) {
611 final View child = getChildAt(i);
612 child.cancelLongPress();
613 }
614 }
615
Michael Jurkadee05892010-07-27 10:01:56 -0700616 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
617 mInterceptTouchListener = listener;
618 }
619
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800620 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700621 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800622 }
623
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800624 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700625 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800626 }
627
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800628 public void setIsHotseat(boolean isHotseat) {
629 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700630 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800631 }
632
Sunny Goyale9b651e2015-04-24 11:44:51 -0700633 public boolean isHotseat() {
634 return mIsHotseat;
635 }
636
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800637 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700638 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700639 final LayoutParams lp = params;
640
Andrew Flynnde38e422012-05-08 11:22:15 -0700641 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800642 if (child instanceof BubbleTextView) {
643 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700644 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800645 }
646
Adam Cohen307fe232012-08-16 17:55:58 -0700647 child.setScaleX(getChildrenScale());
648 child.setScaleY(getChildrenScale());
649
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800650 // Generate an id for each view, this assumes we have at most 256x256 cells
651 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700652 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700653 // If the horizontal or vertical span is set to -1, it is taken to
654 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700655 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
656 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800657
Winson Chungaafa03c2010-06-11 17:34:16 -0700658 child.setId(childId);
Tony Wickhama0628cc2015-10-14 15:23:04 -0700659 if (LOGD) {
660 Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
661 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700662 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700663
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700664 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700665
Winson Chungaafa03c2010-06-11 17:34:16 -0700666 return true;
667 }
668 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800669 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700670
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800671 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700672 public void removeAllViews() {
673 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700674 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700675 }
676
677 @Override
678 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700679 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700680 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700681 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700682 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700683 }
684
685 @Override
686 public void removeView(View view) {
687 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700688 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700689 }
690
691 @Override
692 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700693 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
694 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700695 }
696
697 @Override
698 public void removeViewInLayout(View view) {
699 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700700 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700701 }
702
703 @Override
704 public void removeViews(int start, int count) {
705 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700706 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700707 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700708 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700709 }
710
711 @Override
712 public void removeViewsInLayout(int start, int count) {
713 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700714 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700715 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700716 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800717 }
718
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700719 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700720 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800721 * @param x X coordinate of the point
722 * @param y Y coordinate of the point
723 * @param result Array of 2 ints to hold the x and y coordinate of the cell
724 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700725 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700726 final int hStartPadding = getPaddingLeft();
727 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800728
729 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
730 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
731
Adam Cohend22015c2010-07-26 22:02:18 -0700732 final int xAxis = mCountX;
733 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800734
735 if (result[0] < 0) result[0] = 0;
736 if (result[0] >= xAxis) result[0] = xAxis - 1;
737 if (result[1] < 0) result[1] = 0;
738 if (result[1] >= yAxis) result[1] = yAxis - 1;
739 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700740
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800741 /**
742 * Given a point, return the cell that most closely encloses that point
743 * @param x X coordinate of the point
744 * @param y Y coordinate of the point
745 * @param result Array of 2 ints to hold the x and y coordinate of the cell
746 */
747 void pointToCellRounded(int x, int y, int[] result) {
748 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
749 }
750
751 /**
752 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700753 *
754 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800755 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700756 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800757 * @param result Array of 2 ints to hold the x and y coordinate of the point
758 */
759 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700760 final int hStartPadding = getPaddingLeft();
761 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800762
763 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
764 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
765 }
766
Adam Cohene3e27a82011-04-15 12:07:39 -0700767 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800768 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700769 *
770 * @param cellX X coordinate of the cell
771 * @param cellY Y coordinate of the cell
772 *
773 * @param result Array of 2 ints to hold the x and y coordinate of the point
774 */
775 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700776 regionToCenterPoint(cellX, cellY, 1, 1, result);
777 }
778
779 /**
780 * Given a cell coordinate and span return the point that represents the center of the regio
781 *
782 * @param cellX X coordinate of the cell
783 * @param cellY Y coordinate of the cell
784 *
785 * @param result Array of 2 ints to hold the x and y coordinate of the point
786 */
787 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700788 final int hStartPadding = getPaddingLeft();
789 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700790 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
791 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
792 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
793 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700794 }
795
Adam Cohen19f37922012-03-21 11:59:11 -0700796 /**
797 * Given a cell coordinate and span fills out a corresponding pixel rect
798 *
799 * @param cellX X coordinate of the cell
800 * @param cellY Y coordinate of the cell
801 * @param result Rect in which to write the result
802 */
803 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
804 final int hStartPadding = getPaddingLeft();
805 final int vStartPadding = getPaddingTop();
806 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
807 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
808 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
809 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
810 }
811
Adam Cohen482ed822012-03-02 14:15:13 -0800812 public float getDistanceFromCell(float x, float y, int[] cell) {
813 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700814 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800815 }
816
Adam Cohenf9c184a2016-01-15 16:47:43 -0800817 public int getCellWidth() {
Romain Guy84f296c2009-11-04 15:00:44 -0800818 return mCellWidth;
819 }
820
821 int getCellHeight() {
822 return mCellHeight;
823 }
824
Adam Cohend4844c32011-02-18 19:25:06 -0800825 int getWidthGap() {
826 return mWidthGap;
827 }
828
829 int getHeightGap() {
830 return mHeightGap;
831 }
832
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700833 public void setFixedSize(int width, int height) {
834 mFixedWidth = width;
835 mFixedHeight = height;
836 }
837
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800838 @Override
839 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800840 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800841 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700842 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
843 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700844 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
845 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700846 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700847 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
848 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700849 if (cw != mCellWidth || ch != mCellHeight) {
850 mCellWidth = cw;
851 mCellHeight = ch;
852 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
853 mHeightGap, mCountX, mCountY);
854 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700855 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700856
Winson Chung2d75f122013-09-23 16:53:31 -0700857 int newWidth = childWidthSize;
858 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700859 if (mFixedWidth > 0 && mFixedHeight > 0) {
860 newWidth = mFixedWidth;
861 newHeight = mFixedHeight;
862 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800863 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
864 }
865
Adam Cohend22015c2010-07-26 22:02:18 -0700866 int numWidthGaps = mCountX - 1;
867 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868
Adam Cohen234c4cd2011-07-17 21:03:04 -0700869 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700870 int hSpace = childWidthSize;
871 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700872 int hFreeSpace = hSpace - (mCountX * mCellWidth);
873 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700874 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
875 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700876 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
877 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700878 } else {
879 mWidthGap = mOriginalWidthGap;
880 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700881 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700882
883 // Make the feedback view large enough to hold the blur bitmap.
884 mTouchFeedbackView.measure(
885 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
886 MeasureSpec.EXACTLY),
887 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
888 MeasureSpec.EXACTLY));
889
890 mShortcutsAndWidgets.measure(
891 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
892 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
893
894 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
895 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700896 if (mFixedWidth > 0 && mFixedHeight > 0) {
897 setMeasuredDimension(maxWidth, maxHeight);
898 } else {
899 setMeasuredDimension(widthSize, heightSize);
900 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800901 }
902
903 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700904 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Tony Wickham26b01422015-11-10 14:44:32 -0800905 boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
906 ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
907 int left = getPaddingLeft();
908 if (!isFullscreen) {
Tony Wickhama501d492015-11-03 18:05:01 -0800909 left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
Tony Wickham26b01422015-11-10 14:44:32 -0800910 }
Winson Chung38848ca2013-10-08 12:03:44 -0700911 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700912
913 mTouchFeedbackView.layout(left, top,
914 left + mTouchFeedbackView.getMeasuredWidth(),
915 top + mTouchFeedbackView.getMeasuredHeight());
916 mShortcutsAndWidgets.layout(left, top,
917 left + r - l,
918 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800919 }
920
Tony Wickhama501d492015-11-03 18:05:01 -0800921 /**
922 * Returns the amount of space left over after subtracting padding and cells. This space will be
923 * very small, a few pixels at most, and is a result of rounding down when calculating the cell
924 * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
925 */
926 public int getUnusedHorizontalSpace() {
927 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
928 }
929
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800930 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700931 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
932 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700933
934 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700935 mBackground.getPadding(mTempRect);
936 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
937 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700938 }
939
940 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800941 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700942 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800943 }
944
945 @Override
946 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700947 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800948 }
949
Michael Jurka5f1c5092010-09-03 14:15:02 -0700950 public float getBackgroundAlpha() {
951 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700952 }
953
Michael Jurka5f1c5092010-09-03 14:15:02 -0700954 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800955 if (mBackgroundAlpha != alpha) {
956 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700957 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800958 }
Michael Jurkadee05892010-07-27 10:01:56 -0700959 }
960
Sunny Goyal2805e632015-05-20 15:35:32 -0700961 @Override
962 protected boolean verifyDrawable(Drawable who) {
963 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
964 }
965
Michael Jurkaa52570f2012-03-20 03:18:20 -0700966 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700967 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700968 }
969
Michael Jurkaa52570f2012-03-20 03:18:20 -0700970 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700971 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700972 }
973
Patrick Dubroy440c3602010-07-13 17:50:32 -0700974 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700975 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700976 }
977
Adam Cohen76fc0852011-06-17 13:26:23 -0700978 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800979 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700980 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800981 boolean[][] occupied = mOccupied;
982 if (!permanent) {
983 occupied = mTmpOccupied;
984 }
985
Adam Cohen19f37922012-03-21 11:59:11 -0700986 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700987 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
988 final ItemInfo info = (ItemInfo) child.getTag();
989
990 // We cancel any existing animations
991 if (mReorderAnimators.containsKey(lp)) {
992 mReorderAnimators.get(lp).cancel();
993 mReorderAnimators.remove(lp);
994 }
995
Adam Cohen482ed822012-03-02 14:15:13 -0800996 final int oldX = lp.x;
997 final int oldY = lp.y;
998 if (adjustOccupied) {
999 occupied[lp.cellX][lp.cellY] = false;
1000 occupied[cellX][cellY] = true;
1001 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001002 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001003 if (permanent) {
1004 lp.cellX = info.cellX = cellX;
1005 lp.cellY = info.cellY = cellY;
1006 } else {
1007 lp.tmpCellX = cellX;
1008 lp.tmpCellY = cellY;
1009 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001010 clc.setupLp(lp);
1011 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001012 final int newX = lp.x;
1013 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001014
Adam Cohen76fc0852011-06-17 13:26:23 -07001015 lp.x = oldX;
1016 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001017
Adam Cohen482ed822012-03-02 14:15:13 -08001018 // Exit early if we're not actually moving the view
1019 if (oldX == newX && oldY == newY) {
1020 lp.isLockedToGrid = true;
1021 return true;
1022 }
1023
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001024 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001025 va.setDuration(duration);
1026 mReorderAnimators.put(lp, va);
1027
1028 va.addUpdateListener(new AnimatorUpdateListener() {
1029 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001030 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001031 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001032 lp.x = (int) ((1 - r) * oldX + r * newX);
1033 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001034 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001035 }
1036 });
Adam Cohen482ed822012-03-02 14:15:13 -08001037 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001038 boolean cancelled = false;
1039 public void onAnimationEnd(Animator animation) {
1040 // If the animation was cancelled, it means that another animation
1041 // has interrupted this one, and we don't want to lock the item into
1042 // place just yet.
1043 if (!cancelled) {
1044 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001045 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001046 }
1047 if (mReorderAnimators.containsKey(lp)) {
1048 mReorderAnimators.remove(lp);
1049 }
1050 }
1051 public void onAnimationCancel(Animator animation) {
1052 cancelled = true;
1053 }
1054 });
Adam Cohen482ed822012-03-02 14:15:13 -08001055 va.setStartDelay(delay);
1056 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001057 return true;
1058 }
1059 return false;
1060 }
1061
Tony Wickhama501d492015-11-03 18:05:01 -08001062 void visualizeDropLocation(View v, Bitmap dragOutline, int cellX, int cellY, int spanX,
1063 int spanY, boolean resize, DropTarget.DragObject dragObject) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001064 final int oldDragCellX = mDragCell[0];
1065 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001066
Adam Cohen2801caf2011-05-13 20:57:39 -07001067 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001068 return;
1069 }
1070
Adam Cohen482ed822012-03-02 14:15:13 -08001071 if (cellX != oldDragCellX || cellY != oldDragCellY) {
Sunny Goyale78e3d72015-09-24 11:23:31 -07001072 Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
1073 Rect dragRegion = dragObject.dragView.getDragRegion();
1074
Adam Cohen482ed822012-03-02 14:15:13 -08001075 mDragCell[0] = cellX;
1076 mDragCell[1] = cellY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001077
Joe Onorato4be866d2010-10-10 11:26:02 -07001078 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001079 mDragOutlineAnims[oldIndex].animateOut();
1080 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001081 Rect r = mDragOutlines[mDragOutlineCurrent];
Sunny Goyal106bf642015-07-16 12:18:06 -07001082
Adam Cohend41fbf52012-02-16 23:53:59 -08001083 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001084 cellToRect(cellX, cellY, spanX, spanY, r);
Sunny Goyal106bf642015-07-16 12:18:06 -07001085 } else {
1086 // Find the top left corner of the rect the object will occupy
1087 final int[] topLeft = mTmpPoint;
1088 cellToPoint(cellX, cellY, topLeft);
1089
1090 int left = topLeft[0];
1091 int top = topLeft[1];
1092
1093 if (v != null && dragOffset == null) {
1094 // When drawing the drag outline, it did not account for margin offsets
1095 // added by the view's parent.
1096 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1097 left += lp.leftMargin;
1098 top += lp.topMargin;
1099
1100 // Offsets due to the size difference between the View and the dragOutline.
1101 // There is a size difference to account for the outer blur, which may lie
1102 // outside the bounds of the view.
1103 top += (v.getHeight() - dragOutline.getHeight()) / 2;
1104 // We center about the x axis
1105 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1106 - dragOutline.getWidth()) / 2;
1107 } else {
1108 if (dragOffset != null && dragRegion != null) {
1109 // Center the drag region *horizontally* in the cell and apply a drag
1110 // outline offset
1111 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1112 - dragRegion.width()) / 2;
1113 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1114 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1115 top += dragOffset.y + cellPaddingY;
1116 } else {
1117 // Center the drag outline in the cell
1118 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1119 - dragOutline.getWidth()) / 2;
1120 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1121 - dragOutline.getHeight()) / 2;
1122 }
1123 }
1124 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
Adam Cohend41fbf52012-02-16 23:53:59 -08001125 }
Winson Chung150fbab2010-09-29 17:14:26 -07001126
Sunny Goyal106bf642015-07-16 12:18:06 -07001127 Utilities.scaleRectAboutCenter(r, getChildrenScale());
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001128 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1129 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Sunny Goyale78e3d72015-09-24 11:23:31 -07001130
1131 if (dragObject.stateAnnouncer != null) {
1132 String msg;
1133 if (isHotseat()) {
1134 msg = getContext().getString(R.string.move_to_hotseat_position,
1135 Math.max(cellX, cellY) + 1);
1136 } else {
1137 msg = getContext().getString(R.string.move_to_empty_cell,
1138 cellY + 1, cellX + 1);
1139 }
1140 dragObject.stateAnnouncer.announce(msg);
1141 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001142 }
1143 }
1144
Adam Cohene0310962011-04-18 16:15:31 -07001145 public void clearDragOutlines() {
1146 final int oldIndex = mDragOutlineCurrent;
1147 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001148 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001149 }
1150
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001151 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001152 * Find a vacant area that will fit the given bounds nearest the requested
1153 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001154 *
Romain Guy51afc022009-05-04 18:03:43 -07001155 * @param pixelX The X location at which you want to search for a vacant area.
1156 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001157 * @param minSpanX The minimum horizontal span required
1158 * @param minSpanY The minimum vertical span required
1159 * @param spanX Horizontal span of the object.
1160 * @param spanY Vertical span of the object.
1161 * @param result Array in which to place the result, or null (in which case a new array will
1162 * be allocated)
1163 * @return The X, Y cell of a vacant area that can contain this object,
1164 * nearest the requested location.
1165 */
1166 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1167 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001168 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001169 result, resultSpan);
1170 }
1171
Adam Cohend41fbf52012-02-16 23:53:59 -08001172 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1173 private void lazyInitTempRectStack() {
1174 if (mTempRectStack.isEmpty()) {
1175 for (int i = 0; i < mCountX * mCountY; i++) {
1176 mTempRectStack.push(new Rect());
1177 }
1178 }
1179 }
Adam Cohen482ed822012-03-02 14:15:13 -08001180
Adam Cohend41fbf52012-02-16 23:53:59 -08001181 private void recycleTempRects(Stack<Rect> used) {
1182 while (!used.isEmpty()) {
1183 mTempRectStack.push(used.pop());
1184 }
1185 }
1186
1187 /**
1188 * Find a vacant area that will fit the given bounds nearest the requested
1189 * cell location. Uses Euclidean distance to score multiple vacant areas.
1190 *
1191 * @param pixelX The X location at which you want to search for a vacant area.
1192 * @param pixelY The Y location at which you want to search for a vacant area.
1193 * @param minSpanX The minimum horizontal span required
1194 * @param minSpanY The minimum vertical span required
1195 * @param spanX Horizontal span of the object.
1196 * @param spanY Vertical span of the object.
1197 * @param ignoreOccupied If true, the result can be an occupied cell
1198 * @param result Array in which to place the result, or null (in which case a new array will
1199 * be allocated)
1200 * @return The X, Y cell of a vacant area that can contain this object,
1201 * nearest the requested location.
1202 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001203 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1204 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001205 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001206
Adam Cohene3e27a82011-04-15 12:07:39 -07001207 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1208 // to the center of the item, but we are searching based on the top-left cell, so
1209 // we translate the point over to correspond to the top-left.
1210 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1211 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1212
Jeff Sharkey70864282009-04-07 21:08:40 -07001213 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001214 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001215 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001216 final Rect bestRect = new Rect(-1, -1, -1, -1);
1217 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001218
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001219 final int countX = mCountX;
1220 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001221
Adam Cohend41fbf52012-02-16 23:53:59 -08001222 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1223 spanX < minSpanX || spanY < minSpanY) {
1224 return bestXY;
1225 }
1226
1227 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001228 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001229 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1230 int ySize = -1;
1231 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001232 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001233 // First, let's see if this thing fits anywhere
1234 for (int i = 0; i < minSpanX; i++) {
1235 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001236 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001237 continue inner;
1238 }
Michael Jurkac28de512010-08-13 11:27:44 -07001239 }
1240 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001241 xSize = minSpanX;
1242 ySize = minSpanY;
1243
1244 // We know that the item will fit at _some_ acceptable size, now let's see
1245 // how big we can make it. We'll alternate between incrementing x and y spans
1246 // until we hit a limit.
1247 boolean incX = true;
1248 boolean hitMaxX = xSize >= spanX;
1249 boolean hitMaxY = ySize >= spanY;
1250 while (!(hitMaxX && hitMaxY)) {
1251 if (incX && !hitMaxX) {
1252 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001253 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001254 // We can't move out horizontally
1255 hitMaxX = true;
1256 }
1257 }
1258 if (!hitMaxX) {
1259 xSize++;
1260 }
1261 } else if (!hitMaxY) {
1262 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001263 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001264 // We can't move out vertically
1265 hitMaxY = true;
1266 }
1267 }
1268 if (!hitMaxY) {
1269 ySize++;
1270 }
1271 }
1272 hitMaxX |= xSize >= spanX;
1273 hitMaxY |= ySize >= spanY;
1274 incX = !incX;
1275 }
1276 incX = true;
1277 hitMaxX = xSize >= spanX;
1278 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001279 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001280 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001281 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001282
Adam Cohend41fbf52012-02-16 23:53:59 -08001283 // We verify that the current rect is not a sub-rect of any of our previous
1284 // candidates. In this case, the current rect is disqualified in favour of the
1285 // containing rect.
1286 Rect currentRect = mTempRectStack.pop();
1287 currentRect.set(x, y, x + xSize, y + ySize);
1288 boolean contained = false;
1289 for (Rect r : validRegions) {
1290 if (r.contains(currentRect)) {
1291 contained = true;
1292 break;
1293 }
1294 }
1295 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001296 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001297
Adam Cohend41fbf52012-02-16 23:53:59 -08001298 if ((distance <= bestDistance && !contained) ||
1299 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001300 bestDistance = distance;
1301 bestXY[0] = x;
1302 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001303 if (resultSpan != null) {
1304 resultSpan[0] = xSize;
1305 resultSpan[1] = ySize;
1306 }
1307 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001308 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001309 }
1310 }
1311
Adam Cohenc0dcf592011-06-01 15:30:43 -07001312 // Return -1, -1 if no suitable location found
1313 if (bestDistance == Double.MAX_VALUE) {
1314 bestXY[0] = -1;
1315 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001316 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001317 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001318 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001319 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001320
Adam Cohen482ed822012-03-02 14:15:13 -08001321 /**
1322 * Find a vacant area that will fit the given bounds nearest the requested
1323 * cell location, and will also weigh in a suggested direction vector of the
1324 * desired location. This method computers distance based on unit grid distances,
1325 * not pixel distances.
1326 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001327 * @param cellX The X cell nearest to which you want to search for a vacant area.
1328 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001329 * @param spanX Horizontal span of the object.
1330 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001331 * @param direction The favored direction in which the views should move from x, y
Sunny Goyal9eba1fd2015-10-16 08:58:57 -07001332 * @param occupied The array which represents which cells in the CellLayout are occupied
Adam Cohen47a876d2012-03-19 13:21:41 -07001333 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001334 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001335 * @param result Array in which to place the result, or null (in which case a new array will
1336 * be allocated)
1337 * @return The X, Y cell of a vacant area that can contain this object,
1338 * nearest the requested location.
1339 */
1340 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001341 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001342 // Keep track of best-scoring drop area
1343 final int[] bestXY = result != null ? result : new int[2];
1344 float bestDistance = Float.MAX_VALUE;
1345 int bestDirectionScore = Integer.MIN_VALUE;
1346
1347 final int countX = mCountX;
1348 final int countY = mCountY;
1349
1350 for (int y = 0; y < countY - (spanY - 1); y++) {
1351 inner:
1352 for (int x = 0; x < countX - (spanX - 1); x++) {
1353 // First, let's see if this thing fits anywhere
1354 for (int i = 0; i < spanX; i++) {
1355 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001356 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001357 continue inner;
1358 }
1359 }
1360 }
1361
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001362 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001363 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001364 computeDirectionVector(x - cellX, y - cellY, curDirection);
1365 // The direction score is just the dot product of the two candidate direction
1366 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001367 int curDirectionScore = direction[0] * curDirection[0] +
1368 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001369 boolean exactDirectionOnly = false;
1370 boolean directionMatches = direction[0] == curDirection[0] &&
1371 direction[0] == curDirection[0];
1372 if ((directionMatches || !exactDirectionOnly) &&
1373 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001374 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1375 bestDistance = distance;
1376 bestDirectionScore = curDirectionScore;
1377 bestXY[0] = x;
1378 bestXY[1] = y;
1379 }
1380 }
1381 }
1382
1383 // Return -1, -1 if no suitable location found
1384 if (bestDistance == Float.MAX_VALUE) {
1385 bestXY[0] = -1;
1386 bestXY[1] = -1;
1387 }
1388 return bestXY;
1389 }
1390
1391 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001392 int[] direction, ItemConfiguration currentState) {
1393 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001394 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001395 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001396 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1397
Adam Cohen8baab352012-03-20 17:39:21 -07001398 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001399
1400 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001401 c.x = mTempLocation[0];
1402 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001403 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001404 }
Adam Cohen8baab352012-03-20 17:39:21 -07001405 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001406 return success;
1407 }
1408
Adam Cohenf3900c22012-11-16 18:28:11 -08001409 /**
1410 * This helper class defines a cluster of views. It helps with defining complex edges
1411 * of the cluster and determining how those edges interact with other views. The edges
1412 * essentially define a fine-grained boundary around the cluster of views -- like a more
1413 * precise version of a bounding box.
1414 */
1415 private class ViewCluster {
1416 final static int LEFT = 0;
1417 final static int TOP = 1;
1418 final static int RIGHT = 2;
1419 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001420
Adam Cohenf3900c22012-11-16 18:28:11 -08001421 ArrayList<View> views;
1422 ItemConfiguration config;
1423 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001424
Adam Cohenf3900c22012-11-16 18:28:11 -08001425 int[] leftEdge = new int[mCountY];
1426 int[] rightEdge = new int[mCountY];
1427 int[] topEdge = new int[mCountX];
1428 int[] bottomEdge = new int[mCountX];
1429 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1430
1431 @SuppressWarnings("unchecked")
1432 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1433 this.views = (ArrayList<View>) views.clone();
1434 this.config = config;
1435 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001436 }
1437
Adam Cohenf3900c22012-11-16 18:28:11 -08001438 void resetEdges() {
1439 for (int i = 0; i < mCountX; i++) {
1440 topEdge[i] = -1;
1441 bottomEdge[i] = -1;
1442 }
1443 for (int i = 0; i < mCountY; i++) {
1444 leftEdge[i] = -1;
1445 rightEdge[i] = -1;
1446 }
1447 leftEdgeDirty = true;
1448 rightEdgeDirty = true;
1449 bottomEdgeDirty = true;
1450 topEdgeDirty = true;
1451 boundingRectDirty = true;
1452 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001453
Adam Cohenf3900c22012-11-16 18:28:11 -08001454 void computeEdge(int which, int[] edge) {
1455 int count = views.size();
1456 for (int i = 0; i < count; i++) {
1457 CellAndSpan cs = config.map.get(views.get(i));
1458 switch (which) {
1459 case LEFT:
1460 int left = cs.x;
1461 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1462 if (left < edge[j] || edge[j] < 0) {
1463 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001464 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001465 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001466 break;
1467 case RIGHT:
1468 int right = cs.x + cs.spanX;
1469 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1470 if (right > edge[j]) {
1471 edge[j] = right;
1472 }
1473 }
1474 break;
1475 case TOP:
1476 int top = cs.y;
1477 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1478 if (top < edge[j] || edge[j] < 0) {
1479 edge[j] = top;
1480 }
1481 }
1482 break;
1483 case BOTTOM:
1484 int bottom = cs.y + cs.spanY;
1485 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1486 if (bottom > edge[j]) {
1487 edge[j] = bottom;
1488 }
1489 }
1490 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001491 }
1492 }
1493 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001494
1495 boolean isViewTouchingEdge(View v, int whichEdge) {
1496 CellAndSpan cs = config.map.get(v);
1497
1498 int[] edge = getEdge(whichEdge);
1499
1500 switch (whichEdge) {
1501 case LEFT:
1502 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1503 if (edge[i] == cs.x + cs.spanX) {
1504 return true;
1505 }
1506 }
1507 break;
1508 case RIGHT:
1509 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1510 if (edge[i] == cs.x) {
1511 return true;
1512 }
1513 }
1514 break;
1515 case TOP:
1516 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1517 if (edge[i] == cs.y + cs.spanY) {
1518 return true;
1519 }
1520 }
1521 break;
1522 case BOTTOM:
1523 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1524 if (edge[i] == cs.y) {
1525 return true;
1526 }
1527 }
1528 break;
1529 }
1530 return false;
1531 }
1532
1533 void shift(int whichEdge, int delta) {
1534 for (View v: views) {
1535 CellAndSpan c = config.map.get(v);
1536 switch (whichEdge) {
1537 case LEFT:
1538 c.x -= delta;
1539 break;
1540 case RIGHT:
1541 c.x += delta;
1542 break;
1543 case TOP:
1544 c.y -= delta;
1545 break;
1546 case BOTTOM:
1547 default:
1548 c.y += delta;
1549 break;
1550 }
1551 }
1552 resetEdges();
1553 }
1554
1555 public void addView(View v) {
1556 views.add(v);
1557 resetEdges();
1558 }
1559
1560 public Rect getBoundingRect() {
1561 if (boundingRectDirty) {
1562 boolean first = true;
1563 for (View v: views) {
1564 CellAndSpan c = config.map.get(v);
1565 if (first) {
1566 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1567 first = false;
1568 } else {
1569 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1570 }
1571 }
1572 }
1573 return boundingRect;
1574 }
1575
1576 public int[] getEdge(int which) {
1577 switch (which) {
1578 case LEFT:
1579 return getLeftEdge();
1580 case RIGHT:
1581 return getRightEdge();
1582 case TOP:
1583 return getTopEdge();
1584 case BOTTOM:
1585 default:
1586 return getBottomEdge();
1587 }
1588 }
1589
1590 public int[] getLeftEdge() {
1591 if (leftEdgeDirty) {
1592 computeEdge(LEFT, leftEdge);
1593 }
1594 return leftEdge;
1595 }
1596
1597 public int[] getRightEdge() {
1598 if (rightEdgeDirty) {
1599 computeEdge(RIGHT, rightEdge);
1600 }
1601 return rightEdge;
1602 }
1603
1604 public int[] getTopEdge() {
1605 if (topEdgeDirty) {
1606 computeEdge(TOP, topEdge);
1607 }
1608 return topEdge;
1609 }
1610
1611 public int[] getBottomEdge() {
1612 if (bottomEdgeDirty) {
1613 computeEdge(BOTTOM, bottomEdge);
1614 }
1615 return bottomEdge;
1616 }
1617
1618 PositionComparator comparator = new PositionComparator();
1619 class PositionComparator implements Comparator<View> {
1620 int whichEdge = 0;
1621 public int compare(View left, View right) {
1622 CellAndSpan l = config.map.get(left);
1623 CellAndSpan r = config.map.get(right);
1624 switch (whichEdge) {
1625 case LEFT:
1626 return (r.x + r.spanX) - (l.x + l.spanX);
1627 case RIGHT:
1628 return l.x - r.x;
1629 case TOP:
1630 return (r.y + r.spanY) - (l.y + l.spanY);
1631 case BOTTOM:
1632 default:
1633 return l.y - r.y;
1634 }
1635 }
1636 }
1637
1638 public void sortConfigurationForEdgePush(int edge) {
1639 comparator.whichEdge = edge;
1640 Collections.sort(config.sortedViews, comparator);
1641 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001642 }
1643
Adam Cohenf3900c22012-11-16 18:28:11 -08001644 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1645 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001646
Adam Cohenf3900c22012-11-16 18:28:11 -08001647 ViewCluster cluster = new ViewCluster(views, currentState);
1648 Rect clusterRect = cluster.getBoundingRect();
1649 int whichEdge;
1650 int pushDistance;
1651 boolean fail = false;
1652
1653 // Determine the edge of the cluster that will be leading the push and how far
1654 // the cluster must be shifted.
1655 if (direction[0] < 0) {
1656 whichEdge = ViewCluster.LEFT;
1657 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001658 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001659 whichEdge = ViewCluster.RIGHT;
1660 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1661 } else if (direction[1] < 0) {
1662 whichEdge = ViewCluster.TOP;
1663 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1664 } else {
1665 whichEdge = ViewCluster.BOTTOM;
1666 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001667 }
1668
Adam Cohenf3900c22012-11-16 18:28:11 -08001669 // Break early for invalid push distance.
1670 if (pushDistance <= 0) {
1671 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001672 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001673
1674 // Mark the occupied state as false for the group of views we want to move.
1675 for (View v: views) {
1676 CellAndSpan c = currentState.map.get(v);
1677 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1678 }
1679
1680 // We save the current configuration -- if we fail to find a solution we will revert
1681 // to the initial state. The process of finding a solution modifies the configuration
1682 // in place, hence the need for revert in the failure case.
1683 currentState.save();
1684
1685 // The pushing algorithm is simplified by considering the views in the order in which
1686 // they would be pushed by the cluster. For example, if the cluster is leading with its
1687 // left edge, we consider sort the views by their right edge, from right to left.
1688 cluster.sortConfigurationForEdgePush(whichEdge);
1689
1690 while (pushDistance > 0 && !fail) {
1691 for (View v: currentState.sortedViews) {
1692 // For each view that isn't in the cluster, we see if the leading edge of the
1693 // cluster is contacting the edge of that view. If so, we add that view to the
1694 // cluster.
1695 if (!cluster.views.contains(v) && v != dragView) {
1696 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1697 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1698 if (!lp.canReorder) {
1699 // The push solution includes the all apps button, this is not viable.
1700 fail = true;
1701 break;
1702 }
1703 cluster.addView(v);
1704 CellAndSpan c = currentState.map.get(v);
1705
1706 // Adding view to cluster, mark it as not occupied.
1707 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1708 }
1709 }
1710 }
1711 pushDistance--;
1712
1713 // The cluster has been completed, now we move the whole thing over in the appropriate
1714 // direction.
1715 cluster.shift(whichEdge, 1);
1716 }
1717
1718 boolean foundSolution = false;
1719 clusterRect = cluster.getBoundingRect();
1720
1721 // Due to the nature of the algorithm, the only check required to verify a valid solution
1722 // is to ensure that completed shifted cluster lies completely within the cell layout.
1723 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1724 clusterRect.bottom <= mCountY) {
1725 foundSolution = true;
1726 } else {
1727 currentState.restore();
1728 }
1729
1730 // In either case, we set the occupied array as marked for the location of the views
1731 for (View v: cluster.views) {
1732 CellAndSpan c = currentState.map.get(v);
1733 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1734 }
1735
1736 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001737 }
1738
Adam Cohen482ed822012-03-02 14:15:13 -08001739 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001740 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001741 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001742
Adam Cohen8baab352012-03-20 17:39:21 -07001743 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001744 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001745 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001746 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001747 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001748 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001749 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001750 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001751 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001752 }
1753 }
Adam Cohen8baab352012-03-20 17:39:21 -07001754
Adam Cohen8baab352012-03-20 17:39:21 -07001755 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001756 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001757 CellAndSpan c = currentState.map.get(v);
1758 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1759 }
1760
Adam Cohen47a876d2012-03-19 13:21:41 -07001761 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1762 int top = boundingRect.top;
1763 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001764 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001765 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001766 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001767 CellAndSpan c = currentState.map.get(v);
1768 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001769 }
1770
Adam Cohen482ed822012-03-02 14:15:13 -08001771 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1772
Adam Cohenf3900c22012-11-16 18:28:11 -08001773 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1774 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001775
Adam Cohen8baab352012-03-20 17:39:21 -07001776 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001777 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001778 int deltaX = mTempLocation[0] - boundingRect.left;
1779 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001780 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001781 CellAndSpan c = currentState.map.get(v);
1782 c.x += deltaX;
1783 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001784 }
1785 success = true;
1786 }
Adam Cohen8baab352012-03-20 17:39:21 -07001787
1788 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001789 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001790 CellAndSpan c = currentState.map.get(v);
1791 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001792 }
1793 return success;
1794 }
1795
1796 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1797 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1798 }
1799
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001800 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1801 // to push items in each of the cardinal directions, in an order based on the direction vector
1802 // passed.
1803 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1804 int[] direction, View ignoreView, ItemConfiguration solution) {
1805 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001806 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001807 // separately in each of the components.
1808 int temp = direction[1];
1809 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001810
1811 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001812 ignoreView, solution)) {
1813 return true;
1814 }
1815 direction[1] = temp;
1816 temp = direction[0];
1817 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001818
1819 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001820 ignoreView, solution)) {
1821 return true;
1822 }
1823 // Revert the direction
1824 direction[0] = temp;
1825
1826 // Now we try pushing in each component of the opposite direction
1827 direction[0] *= -1;
1828 direction[1] *= -1;
1829 temp = direction[1];
1830 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001831 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001832 ignoreView, solution)) {
1833 return true;
1834 }
1835
1836 direction[1] = temp;
1837 temp = direction[0];
1838 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001839 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001840 ignoreView, solution)) {
1841 return true;
1842 }
1843 // revert the direction
1844 direction[0] = temp;
1845 direction[0] *= -1;
1846 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001847
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001848 } else {
1849 // If the direction vector has a single non-zero component, we push first in the
1850 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001851 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001852 ignoreView, solution)) {
1853 return true;
1854 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001855 // Then we try the opposite direction
1856 direction[0] *= -1;
1857 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001858 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001859 ignoreView, solution)) {
1860 return true;
1861 }
1862 // Switch the direction back
1863 direction[0] *= -1;
1864 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001865
1866 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001867 // to find a solution by pushing along the perpendicular axis.
1868
1869 // Swap the components
1870 int temp = direction[1];
1871 direction[1] = direction[0];
1872 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001873 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001874 ignoreView, solution)) {
1875 return true;
1876 }
1877
1878 // Then we try the opposite direction
1879 direction[0] *= -1;
1880 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001881 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001882 ignoreView, solution)) {
1883 return true;
1884 }
1885 // Switch the direction back
1886 direction[0] *= -1;
1887 direction[1] *= -1;
1888
1889 // Swap the components back
1890 temp = direction[1];
1891 direction[1] = direction[0];
1892 direction[0] = temp;
1893 }
1894 return false;
1895 }
1896
Adam Cohen482ed822012-03-02 14:15:13 -08001897 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001898 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001899 // Return early if get invalid cell positions
1900 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001901
Adam Cohen8baab352012-03-20 17:39:21 -07001902 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001903 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001904
Adam Cohen8baab352012-03-20 17:39:21 -07001905 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001906 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001907 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001908 if (c != null) {
1909 c.x = cellX;
1910 c.y = cellY;
1911 }
Adam Cohen482ed822012-03-02 14:15:13 -08001912 }
Adam Cohen482ed822012-03-02 14:15:13 -08001913 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1914 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001915 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001916 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001917 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001918 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001919 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001920 if (Rect.intersects(r0, r1)) {
1921 if (!lp.canReorder) {
1922 return false;
1923 }
1924 mIntersectingViews.add(child);
1925 }
1926 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001927
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001928 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1929
Winson Chung5f8afe62013-08-12 16:19:28 -07001930 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001931 // we try to find a solution such that no displaced item travels through another item
1932 // without also displacing that item.
1933 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001934 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001935 return true;
1936 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001937
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001938 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001939 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001940 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001941 return true;
1942 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001943
Adam Cohen482ed822012-03-02 14:15:13 -08001944 // Ok, they couldn't move as a block, let's move them individually
1945 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001946 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001947 return false;
1948 }
1949 }
1950 return true;
1951 }
1952
1953 /*
1954 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1955 * the provided point and the provided cell
1956 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001957 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001958 double angle = Math.atan(((float) deltaY) / deltaX);
1959
1960 result[0] = 0;
1961 result[1] = 0;
1962 if (Math.abs(Math.cos(angle)) > 0.5f) {
1963 result[0] = (int) Math.signum(deltaX);
1964 }
1965 if (Math.abs(Math.sin(angle)) > 0.5f) {
1966 result[1] = (int) Math.signum(deltaY);
1967 }
1968 }
1969
Adam Cohen8baab352012-03-20 17:39:21 -07001970 private void copyOccupiedArray(boolean[][] occupied) {
1971 for (int i = 0; i < mCountX; i++) {
1972 for (int j = 0; j < mCountY; j++) {
1973 occupied[i][j] = mOccupied[i][j];
1974 }
1975 }
1976 }
1977
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001978 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001979 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1980 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001981 // Copy the current state into the solution. This solution will be manipulated as necessary.
1982 copyCurrentStateToSolution(solution, false);
1983 // Copy the current occupied array into the temporary occupied array. This array will be
1984 // manipulated as necessary to find a solution.
1985 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001986
1987 // We find the nearest cell into which we would place the dragged item, assuming there's
1988 // nothing in its way.
1989 int result[] = new int[2];
1990 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1991
1992 boolean success = false;
1993 // First we try the exact nearest position of the item being dragged,
1994 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001995 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1996 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001997
1998 if (!success) {
1999 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2000 // x, then 1 in y etc.
2001 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002002 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2003 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002004 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002005 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2006 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002007 }
2008 solution.isSolution = false;
2009 } else {
2010 solution.isSolution = true;
2011 solution.dragViewX = result[0];
2012 solution.dragViewY = result[1];
2013 solution.dragViewSpanX = spanX;
2014 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002015 }
2016 return solution;
2017 }
2018
2019 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002020 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002021 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002022 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002023 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002024 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002025 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002026 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002027 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002028 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002029 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002030 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002031 }
2032 }
2033
2034 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2035 for (int i = 0; i < mCountX; i++) {
2036 for (int j = 0; j < mCountY; j++) {
2037 mTmpOccupied[i][j] = false;
2038 }
2039 }
2040
Michael Jurkaa52570f2012-03-20 03:18:20 -07002041 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002042 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002043 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002044 if (child == dragView) continue;
2045 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002046 CellAndSpan c = solution.map.get(child);
2047 if (c != null) {
2048 lp.tmpCellX = c.x;
2049 lp.tmpCellY = c.y;
2050 lp.cellHSpan = c.spanX;
2051 lp.cellVSpan = c.spanY;
2052 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002053 }
2054 }
2055 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2056 solution.dragViewSpanY, mTmpOccupied, true);
2057 }
2058
2059 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2060 commitDragView) {
2061
2062 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2063 for (int i = 0; i < mCountX; i++) {
2064 for (int j = 0; j < mCountY; j++) {
2065 occupied[i][j] = false;
2066 }
2067 }
2068
Michael Jurkaa52570f2012-03-20 03:18:20 -07002069 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002070 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002071 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002072 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002073 CellAndSpan c = solution.map.get(child);
2074 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002075 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2076 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002077 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002078 }
2079 }
2080 if (commitDragView) {
2081 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2082 solution.dragViewSpanY, occupied, true);
2083 }
2084 }
2085
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002086
2087 // This method starts or changes the reorder preview animations
2088 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2089 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002090 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002091 for (int i = 0; i < childCount; i++) {
2092 View child = mShortcutsAndWidgets.getChildAt(i);
2093 if (child == dragView) continue;
2094 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002095 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2096 != null && !solution.intersectingViews.contains(child);
2097
Adam Cohen19f37922012-03-21 11:59:11 -07002098 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002099 if (c != null && !skip) {
2100 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2101 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002102 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002103 }
2104 }
2105 }
2106
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002107 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002108 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002109 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002110 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002111 float finalDeltaX;
2112 float finalDeltaY;
2113 float initDeltaX;
2114 float initDeltaY;
2115 float finalScale;
2116 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002117 int mode;
2118 boolean repeating = false;
2119 private static final int PREVIEW_DURATION = 300;
2120 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2121
2122 public static final int MODE_HINT = 0;
2123 public static final int MODE_PREVIEW = 1;
2124
Adam Cohene7587d22012-05-24 18:50:02 -07002125 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002126
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002127 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2128 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002129 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2130 final int x0 = mTmpPoint[0];
2131 final int y0 = mTmpPoint[1];
2132 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2133 final int x1 = mTmpPoint[0];
2134 final int y1 = mTmpPoint[1];
2135 final int dX = x1 - x0;
2136 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002137 finalDeltaX = 0;
2138 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002139 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002140 if (dX == dY && dX == 0) {
2141 } else {
2142 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002143 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002144 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002145 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002146 } else {
2147 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002148 finalDeltaX = (int) (- dir * Math.signum(dX) *
2149 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2150 finalDeltaY = (int) (- dir * Math.signum(dY) *
2151 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002152 }
2153 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002154 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002155 initDeltaX = child.getTranslationX();
2156 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002157 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002158 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002159 this.child = child;
2160 }
2161
Adam Cohend024f982012-05-23 18:26:45 -07002162 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002163 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002164 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002165 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002166 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002167 if (finalDeltaX == 0 && finalDeltaY == 0) {
2168 completeAnimationImmediately();
2169 return;
2170 }
Adam Cohen19f37922012-03-21 11:59:11 -07002171 }
Adam Cohend024f982012-05-23 18:26:45 -07002172 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002173 return;
2174 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002175 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002176 a = va;
Tony Wickham9e0702f2015-09-02 14:45:39 -07002177
2178 // Animations are disabled in power save mode, causing the repeated animation to jump
2179 // spastically between beginning and end states. Since this looks bad, we don't repeat
2180 // the animation in power save mode.
Tony Wickham112ac952015-11-12 12:31:50 -08002181 if (!Utilities.isPowerSaverOn(getContext())) {
Tony Wickham9e0702f2015-09-02 14:45:39 -07002182 va.setRepeatMode(ValueAnimator.REVERSE);
2183 va.setRepeatCount(ValueAnimator.INFINITE);
2184 }
2185
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002186 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002187 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002188 va.addUpdateListener(new AnimatorUpdateListener() {
2189 @Override
2190 public void onAnimationUpdate(ValueAnimator animation) {
2191 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002192 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2193 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2194 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002195 child.setTranslationX(x);
2196 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002197 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002198 child.setScaleX(s);
2199 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002200 }
2201 });
2202 va.addListener(new AnimatorListenerAdapter() {
2203 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002204 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002205 initDeltaX = 0;
2206 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002207 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002208 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002209 }
2210 });
Adam Cohen19f37922012-03-21 11:59:11 -07002211 mShakeAnimators.put(child, this);
2212 va.start();
2213 }
2214
Adam Cohend024f982012-05-23 18:26:45 -07002215 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002216 if (a != null) {
2217 a.cancel();
2218 }
Adam Cohen19f37922012-03-21 11:59:11 -07002219 }
Adam Cohene7587d22012-05-24 18:50:02 -07002220
Adam Cohen091440a2015-03-18 14:16:05 -07002221 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002222 if (a != null) {
2223 a.cancel();
2224 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002225
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002226 a = new LauncherViewPropertyAnimator(child)
2227 .scaleX(getChildrenScale())
2228 .scaleY(getChildrenScale())
2229 .translationX(0)
2230 .translationY(0)
2231 .setDuration(REORDER_ANIMATION_DURATION);
2232 a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2233 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002234 }
Adam Cohen19f37922012-03-21 11:59:11 -07002235 }
2236
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002237 private void completeAndClearReorderPreviewAnimations() {
2238 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002239 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002240 }
2241 mShakeAnimators.clear();
2242 }
2243
Adam Cohen482ed822012-03-02 14:15:13 -08002244 private void commitTempPlacement() {
2245 for (int i = 0; i < mCountX; i++) {
2246 for (int j = 0; j < mCountY; j++) {
2247 mOccupied[i][j] = mTmpOccupied[i][j];
2248 }
2249 }
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002250
2251 long screenId = mLauncher.getWorkspace().getIdForScreen(this);
2252 int container = Favorites.CONTAINER_DESKTOP;
2253
2254 if (mLauncher.isHotseatLayout(this)) {
2255 screenId = -1;
2256 container = Favorites.CONTAINER_HOTSEAT;
2257 }
2258
Michael Jurkaa52570f2012-03-20 03:18:20 -07002259 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002260 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002261 View child = mShortcutsAndWidgets.getChildAt(i);
2262 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2263 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002264 // We do a null check here because the item info can be null in the case of the
2265 // AllApps button in the hotseat.
2266 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002267 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2268 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2269 || info.spanY != lp.cellVSpan);
2270
Adam Cohen2acce882012-03-28 19:03:19 -07002271 info.cellX = lp.cellX = lp.tmpCellX;
2272 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002273 info.spanX = lp.cellHSpan;
2274 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002275
2276 if (requiresDbUpdate) {
2277 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
2278 info.cellX, info.cellY, info.spanX, info.spanY);
2279 }
Adam Cohen2acce882012-03-28 19:03:19 -07002280 }
Adam Cohen482ed822012-03-02 14:15:13 -08002281 }
2282 }
2283
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002284 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002285 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002286 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002287 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002288 lp.useTmpCoords = useTempCoords;
2289 }
2290 }
2291
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002292 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002293 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2294 int[] result = new int[2];
2295 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002296 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002297 resultSpan);
2298 if (result[0] >= 0 && result[1] >= 0) {
2299 copyCurrentStateToSolution(solution, false);
2300 solution.dragViewX = result[0];
2301 solution.dragViewY = result[1];
2302 solution.dragViewSpanX = resultSpan[0];
2303 solution.dragViewSpanY = resultSpan[1];
2304 solution.isSolution = true;
2305 } else {
2306 solution.isSolution = false;
2307 }
2308 return solution;
2309 }
2310
2311 public void prepareChildForDrag(View child) {
2312 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002313 }
2314
Adam Cohen19f37922012-03-21 11:59:11 -07002315 /* This seems like it should be obvious and straight-forward, but when the direction vector
2316 needs to match with the notion of the dragView pushing other views, we have to employ
2317 a slightly more subtle notion of the direction vector. The question is what two points is
2318 the vector between? The center of the dragView and its desired destination? Not quite, as
2319 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2320 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2321 or right, which helps make pushing feel right.
2322 */
2323 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2324 int spanY, View dragView, int[] resultDirection) {
2325 int[] targetDestination = new int[2];
2326
2327 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2328 Rect dragRect = new Rect();
2329 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2330 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2331
2332 Rect dropRegionRect = new Rect();
2333 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2334 dragView, dropRegionRect, mIntersectingViews);
2335
2336 int dropRegionSpanX = dropRegionRect.width();
2337 int dropRegionSpanY = dropRegionRect.height();
2338
2339 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2340 dropRegionRect.height(), dropRegionRect);
2341
2342 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2343 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2344
2345 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2346 deltaX = 0;
2347 }
2348 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2349 deltaY = 0;
2350 }
2351
2352 if (deltaX == 0 && deltaY == 0) {
2353 // No idea what to do, give a random direction.
2354 resultDirection[0] = 1;
2355 resultDirection[1] = 0;
2356 } else {
2357 computeDirectionVector(deltaX, deltaY, resultDirection);
2358 }
2359 }
2360
2361 // For a given cell and span, fetch the set of views intersecting the region.
2362 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2363 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2364 if (boundingRect != null) {
2365 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2366 }
2367 intersectingViews.clear();
2368 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2369 Rect r1 = new Rect();
2370 final int count = mShortcutsAndWidgets.getChildCount();
2371 for (int i = 0; i < count; i++) {
2372 View child = mShortcutsAndWidgets.getChildAt(i);
2373 if (child == dragView) continue;
2374 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2375 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2376 if (Rect.intersects(r0, r1)) {
2377 mIntersectingViews.add(child);
2378 if (boundingRect != null) {
2379 boundingRect.union(r1);
2380 }
2381 }
2382 }
2383 }
2384
2385 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2386 View dragView, int[] result) {
2387 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2388 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2389 mIntersectingViews);
2390 return !mIntersectingViews.isEmpty();
2391 }
2392
2393 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002394 completeAndClearReorderPreviewAnimations();
2395 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2396 final int count = mShortcutsAndWidgets.getChildCount();
2397 for (int i = 0; i < count; i++) {
2398 View child = mShortcutsAndWidgets.getChildAt(i);
2399 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2400 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2401 lp.tmpCellX = lp.cellX;
2402 lp.tmpCellY = lp.cellY;
2403 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2404 0, false, false);
2405 }
Adam Cohen19f37922012-03-21 11:59:11 -07002406 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002407 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002408 }
Adam Cohen19f37922012-03-21 11:59:11 -07002409 }
2410
Adam Cohenbebf0422012-04-11 18:06:28 -07002411 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2412 View dragView, int[] direction, boolean commit) {
2413 int[] pixelXY = new int[2];
2414 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2415
2416 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002417 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002418 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2419
2420 setUseTempCoords(true);
2421 if (swapSolution != null && swapSolution.isSolution) {
2422 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2423 // committing anything or animating anything as we just want to determine if a solution
2424 // exists
2425 copySolutionToTempState(swapSolution, dragView);
2426 setItemPlacementDirty(true);
2427 animateItemsToSolution(swapSolution, dragView, commit);
2428
2429 if (commit) {
2430 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002431 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002432 setItemPlacementDirty(false);
2433 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002434 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2435 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002436 }
2437 mShortcutsAndWidgets.requestLayout();
2438 }
2439 return swapSolution.isSolution;
2440 }
2441
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002442 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002443 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002444 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002445 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002446
2447 if (resultSpan == null) {
2448 resultSpan = new int[2];
2449 }
2450
Adam Cohen19f37922012-03-21 11:59:11 -07002451 // When we are checking drop validity or actually dropping, we don't recompute the
2452 // direction vector, since we want the solution to match the preview, and it's possible
2453 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002454 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2455 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002456 mDirectionVector[0] = mPreviousReorderDirection[0];
2457 mDirectionVector[1] = mPreviousReorderDirection[1];
2458 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002459 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2460 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2461 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002462 }
2463 } else {
2464 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2465 mPreviousReorderDirection[0] = mDirectionVector[0];
2466 mPreviousReorderDirection[1] = mDirectionVector[1];
2467 }
2468
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002469 // Find a solution involving pushing / displacing any items in the way
2470 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002471 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2472
2473 // We attempt the approach which doesn't shuffle views at all
2474 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2475 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2476
2477 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002478
2479 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2480 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002481 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2482 finalSolution = swapSolution;
2483 } else if (noShuffleSolution.isSolution) {
2484 finalSolution = noShuffleSolution;
2485 }
2486
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002487 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002488 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002489 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2490 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002491 result[0] = finalSolution.dragViewX;
2492 result[1] = finalSolution.dragViewY;
2493 resultSpan[0] = finalSolution.dragViewSpanX;
2494 resultSpan[1] = finalSolution.dragViewSpanY;
2495 } else {
2496 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2497 }
2498 return result;
2499 }
2500
Adam Cohen482ed822012-03-02 14:15:13 -08002501 boolean foundSolution = true;
2502 if (!DESTRUCTIVE_REORDER) {
2503 setUseTempCoords(true);
2504 }
2505
2506 if (finalSolution != null) {
2507 result[0] = finalSolution.dragViewX;
2508 result[1] = finalSolution.dragViewY;
2509 resultSpan[0] = finalSolution.dragViewSpanX;
2510 resultSpan[1] = finalSolution.dragViewSpanY;
2511
2512 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2513 // committing anything or animating anything as we just want to determine if a solution
2514 // exists
2515 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2516 if (!DESTRUCTIVE_REORDER) {
2517 copySolutionToTempState(finalSolution, dragView);
2518 }
2519 setItemPlacementDirty(true);
2520 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2521
Adam Cohen19f37922012-03-21 11:59:11 -07002522 if (!DESTRUCTIVE_REORDER &&
2523 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002524 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002525 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002526 setItemPlacementDirty(false);
2527 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002528 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2529 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002530 }
2531 }
2532 } else {
2533 foundSolution = false;
2534 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2535 }
2536
2537 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2538 setUseTempCoords(false);
2539 }
Adam Cohen482ed822012-03-02 14:15:13 -08002540
Michael Jurkaa52570f2012-03-20 03:18:20 -07002541 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002542 return result;
2543 }
2544
Adam Cohen19f37922012-03-21 11:59:11 -07002545 void setItemPlacementDirty(boolean dirty) {
2546 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002547 }
Adam Cohen19f37922012-03-21 11:59:11 -07002548 boolean isItemPlacementDirty() {
2549 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002550 }
2551
Adam Cohen091440a2015-03-18 14:16:05 -07002552 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002553 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002554 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2555 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002556 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002557 boolean isSolution = false;
2558 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2559
Adam Cohenf3900c22012-11-16 18:28:11 -08002560 void save() {
2561 // Copy current state into savedMap
2562 for (View v: map.keySet()) {
2563 map.get(v).copy(savedMap.get(v));
2564 }
2565 }
2566
2567 void restore() {
2568 // Restore current state from savedMap
2569 for (View v: savedMap.keySet()) {
2570 savedMap.get(v).copy(map.get(v));
2571 }
2572 }
2573
2574 void add(View v, CellAndSpan cs) {
2575 map.put(v, cs);
2576 savedMap.put(v, new CellAndSpan());
2577 sortedViews.add(v);
2578 }
2579
Adam Cohen482ed822012-03-02 14:15:13 -08002580 int area() {
2581 return dragViewSpanX * dragViewSpanY;
2582 }
Adam Cohen8baab352012-03-20 17:39:21 -07002583 }
2584
2585 private class CellAndSpan {
2586 int x, y;
2587 int spanX, spanY;
2588
Adam Cohenf3900c22012-11-16 18:28:11 -08002589 public CellAndSpan() {
2590 }
2591
2592 public void copy(CellAndSpan copy) {
2593 copy.x = x;
2594 copy.y = y;
2595 copy.spanX = spanX;
2596 copy.spanY = spanY;
2597 }
2598
Adam Cohen8baab352012-03-20 17:39:21 -07002599 public CellAndSpan(int x, int y, int spanX, int spanY) {
2600 this.x = x;
2601 this.y = y;
2602 this.spanX = spanX;
2603 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002604 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002605
2606 public String toString() {
2607 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2608 }
2609
Adam Cohen482ed822012-03-02 14:15:13 -08002610 }
2611
Adam Cohendf035382011-04-11 17:22:04 -07002612 /**
Adam Cohendf035382011-04-11 17:22:04 -07002613 * Find a starting cell position that will fit the given bounds nearest the requested
2614 * cell location. Uses Euclidean distance to score multiple vacant areas.
2615 *
2616 * @param pixelX The X location at which you want to search for a vacant area.
2617 * @param pixelY The Y location at which you want to search for a vacant area.
2618 * @param spanX Horizontal span of the object.
2619 * @param spanY Vertical span of the object.
2620 * @param ignoreView Considers space occupied by this view as unoccupied
2621 * @param result Previously returned value to possibly recycle.
2622 * @return The X, Y cell of a vacant area that can contain this object,
2623 * nearest the requested location.
2624 */
Adam Cohenf9c184a2016-01-15 16:47:43 -08002625 public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002626 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002627 }
2628
Michael Jurka0280c3b2010-09-17 15:00:07 -07002629 boolean existsEmptyCell() {
2630 return findCellForSpan(null, 1, 1);
2631 }
2632
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002633 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002634 * Finds the upper-left coordinate of the first rectangle in the grid that can
2635 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2636 * then this method will only return coordinates for rectangles that contain the cell
2637 * (intersectX, intersectY)
2638 *
2639 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2640 * can be found.
2641 * @param spanX The horizontal span of the cell we want to find.
2642 * @param spanY The vertical span of the cell we want to find.
2643 *
2644 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002645 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002646 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002647 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002648 final int endX = mCountX - (spanX - 1);
2649 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002650
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002651 for (int y = 0; y < endY && !foundCell; y++) {
2652 inner:
2653 for (int x = 0; x < endX; x++) {
2654 for (int i = 0; i < spanX; i++) {
2655 for (int j = 0; j < spanY; j++) {
2656 if (mOccupied[x + i][y + j]) {
2657 // small optimization: we can skip to after the column we just found
2658 // an occupied cell
2659 x += i;
2660 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002661 }
2662 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002663 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002664 if (cellXY != null) {
2665 cellXY[0] = x;
2666 cellXY[1] = y;
2667 }
2668 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002669 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002670 }
2671 }
2672
Michael Jurka28750fb2010-09-24 17:43:49 -07002673 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002674 }
2675
2676 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002677 * A drag event has begun over this layout.
2678 * It may have begun over this layout (in which case onDragChild is called first),
2679 * or it may have begun on another layout.
2680 */
2681 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002682 mDragging = true;
2683 }
2684
2685 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002686 * Called when drag has left this CellLayout or has been completed (successfully or not)
2687 */
2688 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002689 // This can actually be called when we aren't in a drag, e.g. when adding a new
2690 // item to this layout via the customize drawer.
2691 // Guard against that case.
2692 if (mDragging) {
2693 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002694 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002695
2696 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002697 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002698 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2699 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002700 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002701 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002702 }
2703
2704 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002705 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002706 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002707 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 *
2709 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002710 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002711 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002712 if (child != null) {
2713 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002714 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002715 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002716 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002717 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002718 }
2719
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002720 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002721 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002722 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002723 * @param cellX X coordinate of upper left corner expressed as a cell position
2724 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002725 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002726 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002727 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002728 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002729 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002730 final int cellWidth = mCellWidth;
2731 final int cellHeight = mCellHeight;
2732 final int widthGap = mWidthGap;
2733 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002734
Winson Chung4b825dcd2011-06-19 12:41:22 -07002735 final int hStartPadding = getPaddingLeft();
2736 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002737
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002738 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2739 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2740
2741 int x = hStartPadding + cellX * (cellWidth + widthGap);
2742 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002743
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002744 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002745 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002746
Michael Jurka0280c3b2010-09-17 15:00:07 -07002747 private void clearOccupiedCells() {
2748 for (int x = 0; x < mCountX; x++) {
2749 for (int y = 0; y < mCountY; y++) {
2750 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002751 }
2752 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002753 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002754
Adam Cohend4844c32011-02-18 19:25:06 -08002755 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002756 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002757 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002758 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002759 }
2760
Adam Cohend4844c32011-02-18 19:25:06 -08002761 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002762 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002763 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002764 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002765 }
2766
Adam Cohen482ed822012-03-02 14:15:13 -08002767 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2768 boolean value) {
2769 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002770 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2771 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002772 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002773 }
2774 }
2775 }
2776
Adam Cohen2801caf2011-05-13 20:57:39 -07002777 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002778 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002779 (Math.max((mCountX - 1), 0) * mWidthGap);
2780 }
2781
2782 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002783 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002784 (Math.max((mCountY - 1), 0) * mHeightGap);
2785 }
2786
Michael Jurka66d72172011-04-12 16:29:25 -07002787 public boolean isOccupied(int x, int y) {
2788 if (x < mCountX && y < mCountY) {
2789 return mOccupied[x][y];
2790 } else {
2791 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2792 }
2793 }
2794
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002795 @Override
2796 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2797 return new CellLayout.LayoutParams(getContext(), attrs);
2798 }
2799
2800 @Override
2801 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2802 return p instanceof CellLayout.LayoutParams;
2803 }
2804
2805 @Override
2806 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2807 return new CellLayout.LayoutParams(p);
2808 }
2809
2810 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2811 /**
2812 * Horizontal location of the item in the grid.
2813 */
2814 @ViewDebug.ExportedProperty
2815 public int cellX;
2816
2817 /**
2818 * Vertical location of the item in the grid.
2819 */
2820 @ViewDebug.ExportedProperty
2821 public int cellY;
2822
2823 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002824 * Temporary horizontal location of the item in the grid during reorder
2825 */
2826 public int tmpCellX;
2827
2828 /**
2829 * Temporary vertical location of the item in the grid during reorder
2830 */
2831 public int tmpCellY;
2832
2833 /**
2834 * Indicates that the temporary coordinates should be used to layout the items
2835 */
2836 public boolean useTmpCoords;
2837
2838 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002839 * Number of cells spanned horizontally by the item.
2840 */
2841 @ViewDebug.ExportedProperty
2842 public int cellHSpan;
2843
2844 /**
2845 * Number of cells spanned vertically by the item.
2846 */
2847 @ViewDebug.ExportedProperty
2848 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002849
Adam Cohen1b607ed2011-03-03 17:26:50 -08002850 /**
2851 * Indicates whether the item will set its x, y, width and height parameters freely,
2852 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2853 */
Adam Cohend4844c32011-02-18 19:25:06 -08002854 public boolean isLockedToGrid = true;
2855
Adam Cohen482ed822012-03-02 14:15:13 -08002856 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002857 * Indicates that this item should use the full extents of its parent.
2858 */
2859 public boolean isFullscreen = false;
2860
2861 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002862 * Indicates whether this item can be reordered. Always true except in the case of the
2863 * the AllApps button.
2864 */
2865 public boolean canReorder = true;
2866
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002867 // X coordinate of the view in the layout.
2868 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002869 public int x;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002870 // Y coordinate of the view in the layout.
2871 @ViewDebug.ExportedProperty
Vadim Tryshevfedca432015-08-19 17:55:02 -07002872 public int y;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002873
Romain Guy84f296c2009-11-04 15:00:44 -08002874 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002875
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002876 public LayoutParams(Context c, AttributeSet attrs) {
2877 super(c, attrs);
2878 cellHSpan = 1;
2879 cellVSpan = 1;
2880 }
2881
2882 public LayoutParams(ViewGroup.LayoutParams source) {
2883 super(source);
2884 cellHSpan = 1;
2885 cellVSpan = 1;
2886 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002887
2888 public LayoutParams(LayoutParams source) {
2889 super(source);
2890 this.cellX = source.cellX;
2891 this.cellY = source.cellY;
2892 this.cellHSpan = source.cellHSpan;
2893 this.cellVSpan = source.cellVSpan;
2894 }
2895
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002896 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002897 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002898 this.cellX = cellX;
2899 this.cellY = cellY;
2900 this.cellHSpan = cellHSpan;
2901 this.cellVSpan = cellVSpan;
2902 }
2903
Adam Cohen2374abf2013-04-16 14:56:57 -07002904 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2905 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002906 if (isLockedToGrid) {
2907 final int myCellHSpan = cellHSpan;
2908 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002909 int myCellX = useTmpCoords ? tmpCellX : cellX;
2910 int myCellY = useTmpCoords ? tmpCellY : cellY;
2911
2912 if (invertHorizontally) {
2913 myCellX = colCount - myCellX - cellHSpan;
2914 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002915
Adam Cohend4844c32011-02-18 19:25:06 -08002916 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2917 leftMargin - rightMargin;
2918 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2919 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002920 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2921 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002922 }
2923 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002924
Winson Chungaafa03c2010-06-11 17:34:16 -07002925 public String toString() {
2926 return "(" + this.cellX + ", " + this.cellY + ")";
2927 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002928
2929 public void setWidth(int width) {
2930 this.width = width;
2931 }
2932
2933 public int getWidth() {
2934 return width;
2935 }
2936
2937 public void setHeight(int height) {
2938 this.height = height;
2939 }
2940
2941 public int getHeight() {
2942 return height;
2943 }
2944
2945 public void setX(int x) {
2946 this.x = x;
2947 }
2948
2949 public int getX() {
2950 return x;
2951 }
2952
2953 public void setY(int y) {
2954 this.y = y;
2955 }
2956
2957 public int getY() {
2958 return y;
2959 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002960 }
2961
Michael Jurka0280c3b2010-09-17 15:00:07 -07002962 // This class stores info for two purposes:
2963 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2964 // its spanX, spanY, and the screen it is on
2965 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2966 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2967 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002968 public static final class CellInfo {
Adam Cohenf9c184a2016-01-15 16:47:43 -08002969 public View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002970 int cellX = -1;
2971 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002972 int spanX;
2973 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002974 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002975 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002976
Sunny Goyal83a8f042015-05-19 12:52:12 -07002977 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002978 cell = v;
2979 cellX = info.cellX;
2980 cellY = info.cellY;
2981 spanX = info.spanX;
2982 spanY = info.spanY;
2983 screenId = info.screenId;
2984 container = info.container;
2985 }
2986
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002987 @Override
2988 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002989 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2990 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002991 }
2992 }
Michael Jurkad771c962011-08-09 15:00:48 -07002993
Sunny Goyala9116722015-04-29 13:55:58 -07002994 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2995 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2996 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002997
Tony Wickham86930612015-09-09 13:50:40 -07002998 /**
2999 * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
3000 * if necessary).
3001 */
3002 public boolean hasReorderSolution(ItemInfo itemInfo) {
3003 int[] cellPoint = new int[2];
3004 // Check for a solution starting at every cell.
3005 for (int cellX = 0; cellX < getCountX(); cellX++) {
3006 for (int cellY = 0; cellY < getCountY(); cellY++) {
3007 cellToPoint(cellX, cellY, cellPoint);
3008 if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX,
3009 itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null,
3010 true, new ItemConfiguration()).isSolution) {
3011 return true;
3012 }
3013 }
3014 }
3015 return false;
3016 }
3017
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003018 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3019 int x2 = x + spanX - 1;
3020 int y2 = y + spanY - 1;
3021 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3022 return false;
3023 }
3024 for (int i = x; i <= x2; i++) {
3025 for (int j = y; j <= y2; j++) {
3026 if (mOccupied[i][j]) {
3027 return false;
3028 }
3029 }
3030 }
3031
3032 return true;
3033 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003034}