blob: b2d5a3f4f33dcb2f2082b6099f1ef1ffa362568e [file] [log] [blame]
Daniel Sandler325dc232013-06-05 22:57:57 -04001package com.android.launcher3;
Adam Cohend4844c32011-02-18 19:25:06 -08002
3import android.animation.AnimatorSet;
4import android.animation.ObjectAnimator;
5import android.animation.PropertyValuesHolder;
6import android.animation.ValueAnimator;
7import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -08008import android.appwidget.AppWidgetHostView;
Adam Cohend4844c32011-02-18 19:25:06 -08009import android.appwidget.AppWidgetProviderInfo;
10import android.content.Context;
Sunny Goyal756cd262015-08-20 12:33:21 -070011import android.graphics.Point;
Adam Cohen0cf2a7c2011-11-08 15:07:01 -080012import android.graphics.Rect;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070013import android.util.AttributeSet;
Tony Wickham71255bb2016-02-10 16:18:15 -080014import android.view.KeyEvent;
Sunny Goyal6ad72f02016-09-23 11:01:10 -070015import android.view.MotionEvent;
Tony Wickham71255bb2016-02-10 16:18:15 -080016import android.view.View;
Sunny Goyal37920962017-09-28 13:43:24 -070017import android.view.ViewGroup;
Adam Cohend4844c32011-02-18 19:25:06 -080018
Sunny Goyale78e3d72015-09-24 11:23:31 -070019import com.android.launcher3.accessibility.DragViewStateAnnouncer;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070020import com.android.launcher3.dragndrop.DragLayer;
Tony Wickham71255bb2016-02-10 16:18:15 -080021import com.android.launcher3.util.FocusLogic;
Sunny Goyal29947f02017-12-18 13:49:44 -080022import com.android.launcher3.widget.LauncherAppWidgetHostView;
Sunny Goyale78e3d72015-09-24 11:23:31 -070023
Sunny Goyal37920962017-09-28 13:43:24 -070024public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener {
Sunny Goyalba776d52015-05-18 20:52:57 -070025 private static final int SNAP_DURATION = 150;
26 private static final float DIMMED_HANDLE_ALPHA = 0f;
27 private static final float RESIZE_THRESHOLD = 0.66f;
28
Sunny Goyal756cd262015-08-20 12:33:21 -070029 private static final Rect sTmpRect = new Rect();
30
31 // Represents the cell size on the grid in the two orientations.
32 private static Point[] sCellSize;
Sunny Goyalba776d52015-05-18 20:52:57 -070033
Sunny Goyal08ca40f2016-09-13 21:07:31 -070034 private static final int HANDLE_COUNT = 4;
35 private static final int INDEX_LEFT = 0;
36 private static final int INDEX_TOP = 1;
37 private static final int INDEX_RIGHT = 2;
38 private static final int INDEX_BOTTOM = 3;
39
Sunny Goyalba776d52015-05-18 20:52:57 -070040 private final Launcher mLauncher;
Sunny Goyal08ca40f2016-09-13 21:07:31 -070041 private final DragViewStateAnnouncer mStateAnnouncer;
Sunny Goyalba776d52015-05-18 20:52:57 -070042
Sunny Goyal08ca40f2016-09-13 21:07:31 -070043 private final View[] mDragHandles = new View[HANDLE_COUNT];
Sunny Goyalba776d52015-05-18 20:52:57 -070044
Sunny Goyal08ca40f2016-09-13 21:07:31 -070045 private LauncherAppWidgetHostView mWidgetView;
46 private CellLayout mCellLayout;
47 private DragLayer mDragLayer;
48
49 private Rect mWidgetPadding;
Sunny Goyalba776d52015-05-18 20:52:57 -070050
51 private final int mBackgroundPadding;
52 private final int mTouchTargetWidth;
53
54 private final int[] mDirectionVector = new int[2];
55 private final int[] mLastDirectionVector = new int[2];
Adam Cohend4844c32011-02-18 19:25:06 -080056
Sunny Goyal08ca40f2016-09-13 21:07:31 -070057 private final IntRange mTempRange1 = new IntRange();
58 private final IntRange mTempRange2 = new IntRange();
59
60 private final IntRange mDeltaXRange = new IntRange();
61 private final IntRange mBaselineX = new IntRange();
62
63 private final IntRange mDeltaYRange = new IntRange();
64 private final IntRange mBaselineY = new IntRange();
Sunny Goyale78e3d72015-09-24 11:23:31 -070065
Adam Cohend4844c32011-02-18 19:25:06 -080066 private boolean mLeftBorderActive;
67 private boolean mRightBorderActive;
68 private boolean mTopBorderActive;
69 private boolean mBottomBorderActive;
70
Adam Cohend4844c32011-02-18 19:25:06 -080071 private int mResizeMode;
Adam Cohen3cba7222011-03-02 19:03:11 -080072
Adam Cohend4844c32011-02-18 19:25:06 -080073 private int mRunningHInc;
74 private int mRunningVInc;
75 private int mMinHSpan;
76 private int mMinVSpan;
77 private int mDeltaX;
78 private int mDeltaY;
Adam Cohenbebf0422012-04-11 18:06:28 -070079 private int mDeltaXAddOn;
80 private int mDeltaYAddOn;
Adam Cohen1b607ed2011-03-03 17:26:50 -080081
Adam Cohen4459d6b2012-07-13 15:59:15 -070082 private int mTopTouchRegionAdjustment = 0;
83 private int mBottomTouchRegionAdjustment = 0;
84
Sunny Goyal6ad72f02016-09-23 11:01:10 -070085 private int mXDown, mYDown;
86
Sunny Goyal08ca40f2016-09-13 21:07:31 -070087 public AppWidgetResizeFrame(Context context) {
88 this(context, null);
89 }
Adam Cohend4844c32011-02-18 19:25:06 -080090
Sunny Goyal08ca40f2016-09-13 21:07:31 -070091 public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
92 this(context, attrs, 0);
93 }
94
95 public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
96 super(context, attrs, defStyleAttr);
97
98 mLauncher = Launcher.getLauncher(context);
99 mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
100
101 mBackgroundPadding = getResources()
102 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
103 mTouchTargetWidth = 2 * mBackgroundPadding;
104 }
105
106 @Override
107 protected void onFinishInflate() {
108 super.onFinishInflate();
109
Sunny Goyal37920962017-09-28 13:43:24 -0700110 ViewGroup content = (ViewGroup) getChildAt(0);
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700111 for (int i = 0; i < HANDLE_COUNT; i ++) {
Sunny Goyal37920962017-09-28 13:43:24 -0700112 mDragHandles[i] = content.getChildAt(i);
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700113 }
114 }
115
Sunny Goyal37920962017-09-28 13:43:24 -0700116 public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
117 Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
118 AbstractFloatingView.closeAllOpenViews(launcher);
119
120 DragLayer dl = launcher.getDragLayer();
121 AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
122 .inflate(R.layout.app_widget_resize_frame, dl, false);
123 frame.setupForWidget(widget, cellLayout, dl);
124 ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
125
126 dl.addView(frame);
127 frame.mIsOpen = true;
128 frame.snapToWidget(false);
129 }
130
131 private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700132 DragLayer dragLayer) {
Adam Cohend4844c32011-02-18 19:25:06 -0800133 mCellLayout = cellLayout;
134 mWidgetView = widgetView;
Adam Cohen59400422014-03-05 18:07:04 -0800135 LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
136 widgetView.getAppWidgetInfo();
137 mResizeMode = info.resizeMode;
Adam Cohen67882692011-03-11 15:29:03 -0800138 mDragLayer = dragLayer;
Adam Cohen3cba7222011-03-02 19:03:11 -0800139
Sunny Goyal233ee962015-08-03 13:05:01 -0700140 mMinHSpan = info.minSpanX;
141 mMinVSpan = info.minSpanY;
Adam Cohend4844c32011-02-18 19:25:06 -0800142
Sunny Goyal952e63d2017-08-16 04:59:08 -0700143 mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
144 widgetView.getAppWidgetInfo().provider, null);
Adam Cohen59400422014-03-05 18:07:04 -0800145
Adam Cohend4844c32011-02-18 19:25:06 -0800146 if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700147 mDragHandles[INDEX_TOP].setVisibility(GONE);
148 mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
Adam Cohend4844c32011-02-18 19:25:06 -0800149 } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700150 mDragHandles[INDEX_LEFT].setVisibility(GONE);
151 mDragHandles[INDEX_RIGHT].setVisibility(GONE);
Adam Cohen3cba7222011-03-02 19:03:11 -0800152 }
153
Adam Cohenbebf0422012-04-11 18:06:28 -0700154 // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
155 // cells (same if not resized, or different) will be marked as occupied when the resize
156 // frame is dismissed.
157 mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
Tony Wickham71255bb2016-02-10 16:18:15 -0800158
159 setOnKeyListener(this);
Adam Cohend4844c32011-02-18 19:25:06 -0800160 }
161
162 public boolean beginResizeIfPointInRegion(int x, int y) {
163 boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
164 boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700165
Adam Cohen3cba7222011-03-02 19:03:11 -0800166 mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
167 mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
Adam Cohen4459d6b2012-07-13 15:59:15 -0700168 mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
169 mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
170 && verticalActive;
Adam Cohend4844c32011-02-18 19:25:06 -0800171
172 boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
173 || mTopBorderActive || mBottomBorderActive;
174
Adam Cohend4844c32011-02-18 19:25:06 -0800175 if (anyBordersActive) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700176 mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
177 mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
178 mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
179 mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
Adam Cohend4844c32011-02-18 19:25:06 -0800180 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700181
182 if (mLeftBorderActive) {
183 mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
184 } else if (mRightBorderActive) {
185 mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
186 } else {
187 mDeltaXRange.set(0, 0);
188 }
189 mBaselineX.set(getLeft(), getRight());
190
191 if (mTopBorderActive) {
192 mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
193 } else if (mBottomBorderActive) {
194 mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
195 } else {
196 mDeltaYRange.set(0, 0);
197 }
198 mBaselineY.set(getTop(), getBottom());
199
Adam Cohend4844c32011-02-18 19:25:06 -0800200 return anyBordersActive;
201 }
202
Adam Cohen1b607ed2011-03-03 17:26:50 -0800203 /**
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700204 * Based on the deltas, we resize the frame.
Adam Cohen1b607ed2011-03-03 17:26:50 -0800205 */
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700206 public void visualizeResizeForDelta(int deltaX, int deltaY) {
207 mDeltaX = mDeltaXRange.clamp(deltaX);
208 mDeltaY = mDeltaYRange.clamp(deltaY);
Adam Cohend4844c32011-02-18 19:25:06 -0800209
Adam Cohen67882692011-03-11 15:29:03 -0800210 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700211 mDeltaX = mDeltaXRange.clamp(deltaX);
212 mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
213 lp.x = mTempRange1.start;
214 lp.width = mTempRange1.size();
Adam Cohen67882692011-03-11 15:29:03 -0800215
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700216 mDeltaY = mDeltaYRange.clamp(deltaY);
217 mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
218 lp.y = mTempRange1.start;
219 lp.height = mTempRange1.size();
Adam Cohend4844c32011-02-18 19:25:06 -0800220
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700221 resizeWidgetIfNeeded(false);
Jon Miranda097a7252016-12-28 10:49:55 -0800222
223 // When the widget resizes in multi-window mode, the translation value changes to maintain
224 // a center fit. These overrides ensure the resize frame always aligns with the widget view.
225 getSnappedRectRelativeToDragLayer(sTmpRect);
226 if (mLeftBorderActive) {
227 lp.width = sTmpRect.width() + sTmpRect.left - lp.x;
228 }
229 if (mTopBorderActive) {
230 lp.height = sTmpRect.height() + sTmpRect.top - lp.y;
231 }
232 if (mRightBorderActive) {
233 lp.x = sTmpRect.left;
234 }
235 if (mBottomBorderActive) {
236 lp.y = sTmpRect.top;
237 }
238
Adam Cohend4844c32011-02-18 19:25:06 -0800239 requestLayout();
240 }
241
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700242 private static int getSpanIncrement(float deltaFrac) {
243 return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
244 }
245
Adam Cohen1b607ed2011-03-03 17:26:50 -0800246 /**
247 * Based on the current deltas, we determine if and how to resize the widget.
248 */
Adam Cohenbebf0422012-04-11 18:06:28 -0700249 private void resizeWidgetIfNeeded(boolean onDismiss) {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530250 float xThreshold = mCellLayout.getCellWidth();
251 float yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800252
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700253 int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
254 int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
Adam Cohene4b77292011-03-08 18:35:52 -0800255
Adam Cohenbebf0422012-04-11 18:06:28 -0700256 if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800257
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700258 mDirectionVector[0] = 0;
259 mDirectionVector[1] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -0800260
261 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
Adam Cohen1b607ed2011-03-03 17:26:50 -0800262
Adam Cohenbebf0422012-04-11 18:06:28 -0700263 int spanX = lp.cellHSpan;
264 int spanY = lp.cellVSpan;
265 int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
266 int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
267
Adam Cohen1b607ed2011-03-03 17:26:50 -0800268 // For each border, we bound the resizing based on the minimum width, and the maximum
269 // expandability.
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700270 mTempRange1.set(cellX, spanX + cellX);
271 int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
272 hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
273 cellX = mTempRange2.start;
274 spanX = mTempRange2.size();
275 if (hSpanDelta != 0) {
276 mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800277 }
278
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700279 mTempRange1.set(cellY, spanY + cellY);
280 int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
281 vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
282 cellY = mTempRange2.start;
283 spanY = mTempRange2.size();
284 if (vSpanDelta != 0) {
285 mDirectionVector[1] = mTopBorderActive ? -1 : 1;
Adam Cohend4844c32011-02-18 19:25:06 -0800286 }
287
Adam Cohenbebf0422012-04-11 18:06:28 -0700288 if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
Adam Cohend4844c32011-02-18 19:25:06 -0800289
Adam Cohene0489502012-08-27 15:18:53 -0700290 // We always want the final commit to match the feedback, so we make sure to use the
291 // last used direction vector when committing the resize / reorder.
292 if (onDismiss) {
293 mDirectionVector[0] = mLastDirectionVector[0];
294 mDirectionVector[1] = mLastDirectionVector[1];
295 } else {
296 mLastDirectionVector[0] = mDirectionVector[0];
297 mLastDirectionVector[1] = mDirectionVector[1];
298 }
299
Adam Cohenbebf0422012-04-11 18:06:28 -0700300 if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
301 mDirectionVector, onDismiss)) {
Sunny Goyale78e3d72015-09-24 11:23:31 -0700302 if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
303 mStateAnnouncer.announce(
304 mLauncher.getString(R.string.widget_resized, spanX, spanY));
305 }
306
Adam Cohenbebf0422012-04-11 18:06:28 -0700307 lp.tmpCellX = cellX;
308 lp.tmpCellY = cellY;
309 lp.cellHSpan = spanX;
310 lp.cellVSpan = spanY;
311 mRunningVInc += vSpanDelta;
312 mRunningHInc += hSpanDelta;
Sunny Goyale78e3d72015-09-24 11:23:31 -0700313
Adam Cohena897f392012-04-27 18:12:05 -0700314 if (!onDismiss) {
315 updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
316 }
Adam Cohenbebf0422012-04-11 18:06:28 -0700317 }
Adam Cohen67882692011-03-11 15:29:03 -0800318 mWidgetView.requestLayout();
Adam Cohend4844c32011-02-18 19:25:06 -0800319 }
320
Adam Cohena897f392012-04-27 18:12:05 -0700321 static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
322 int spanX, int spanY) {
Sunny Goyalba776d52015-05-18 20:52:57 -0700323 getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
324 widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
325 sTmpRect.right, sTmpRect.bottom);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700326 }
327
Sunny Goyal16466f12016-03-10 05:34:30 -0800328 public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
Sunny Goyal756cd262015-08-20 12:33:21 -0700329 if (sCellSize == null) {
Sunny Goyal87f784c2017-01-11 10:48:34 -0800330 InvariantDeviceProfile inv = LauncherAppState.getIDP(context);
Sunny Goyal756cd262015-08-20 12:33:21 -0700331
332 // Initiate cell sizes.
333 sCellSize = new Point[2];
334 sCellSize[0] = inv.landscapeProfile.getCellSize();
335 sCellSize[1] = inv.portraitProfile.getCellSize();
336 }
337
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700338 if (rect == null) {
339 rect = new Rect();
340 }
Sunny Goyal16466f12016-03-10 05:34:30 -0800341 final float density = context.getResources().getDisplayMetrics().density;
Adam Cohena897f392012-04-27 18:12:05 -0700342
343 // Compute landscape size
Sunny Goyal756cd262015-08-20 12:33:21 -0700344 int landWidth = (int) ((spanX * sCellSize[0].x) / density);
345 int landHeight = (int) ((spanY * sCellSize[0].y) / density);
Adam Cohena897f392012-04-27 18:12:05 -0700346
347 // Compute portrait size
Sunny Goyal756cd262015-08-20 12:33:21 -0700348 int portWidth = (int) ((spanX * sCellSize[1].x) / density);
349 int portHeight = (int) ((spanY * sCellSize[1].y) / density);
Adam Cohen9e05a5e2012-09-10 15:53:09 -0700350 rect.set(portWidth, landHeight, landWidth, portHeight);
351 return rect;
Adam Cohena897f392012-04-27 18:12:05 -0700352 }
353
Sunny Goyal988ad272017-08-18 02:05:01 -0700354 @Override
355 protected void onDetachedFromWindow() {
356 super.onDetachedFromWindow();
357
358 // We are done with resizing the widget. Save the widget size & position to LauncherModel
Adam Cohenbebf0422012-04-11 18:06:28 -0700359 resizeWidgetIfNeeded(true);
Adam Cohenbebf0422012-04-11 18:06:28 -0700360 }
Adam Cohend4844c32011-02-18 19:25:06 -0800361
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700362 private void onTouchUp() {
Sunny Goyalaa8a8712016-11-20 15:26:01 +0530363 int xThreshold = mCellLayout.getCellWidth();
364 int yThreshold = mCellLayout.getCellHeight();
Adam Cohend4844c32011-02-18 19:25:06 -0800365
Jon Miranda097a7252016-12-28 10:49:55 -0800366 mDeltaXAddOn = mRunningHInc * xThreshold;
367 mDeltaYAddOn = mRunningVInc * yThreshold;
Adam Cohenbebf0422012-04-11 18:06:28 -0700368 mDeltaX = 0;
369 mDeltaY = 0;
370
Adam Cohend4844c32011-02-18 19:25:06 -0800371 post(new Runnable() {
Adam Cohenbebf0422012-04-11 18:06:28 -0700372 @Override
Adam Cohend4844c32011-02-18 19:25:06 -0800373 public void run() {
374 snapToWidget(true);
375 }
376 });
377 }
378
Jon Miranda097a7252016-12-28 10:49:55 -0800379 /**
380 * Returns the rect of this view when the frame is snapped around the widget, with the bounds
381 * relative to the {@link DragLayer}.
382 */
383 private void getSnappedRectRelativeToDragLayer(Rect out) {
Jon Miranda21266912016-12-19 14:12:05 -0800384 float scale = mWidgetView.getScaleToFit();
Adam Cohen37b59ff2011-06-13 17:13:42 -0700385
Jon Miranda097a7252016-12-28 10:49:55 -0800386 mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
Adam Cohend6e7aa32013-07-09 15:32:37 -0700387
Jon Miranda097a7252016-12-28 10:49:55 -0800388 int width = 2 * mBackgroundPadding
389 + (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
390 int height = 2 * mBackgroundPadding
391 + (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
Jon Miranda26e4d172016-12-12 15:43:18 -0800392
Jon Miranda097a7252016-12-28 10:49:55 -0800393 int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
394 int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
395
396 out.left = x;
397 out.top = y;
398 out.right = out.left + width;
399 out.bottom = out.top + height;
400 }
401
Sunny Goyal37920962017-09-28 13:43:24 -0700402 private void snapToWidget(boolean animate) {
Jon Miranda097a7252016-12-28 10:49:55 -0800403 getSnappedRectRelativeToDragLayer(sTmpRect);
404 int newWidth = sTmpRect.width();
405 int newHeight = sTmpRect.height();
406 int newX = sTmpRect.left;
407 int newY = sTmpRect.top;
Adam Cohen3cba7222011-03-02 19:03:11 -0800408
Sunny Goyalba776d52015-05-18 20:52:57 -0700409 // We need to make sure the frame's touchable regions lie fully within the bounds of the
Adam Cohen4459d6b2012-07-13 15:59:15 -0700410 // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
411 // down accordingly to provide a proper touch target.
Adam Cohen3cba7222011-03-02 19:03:11 -0800412 if (newY < 0) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700413 // In this case we shift the touch region down to start at the top of the DragLayer
414 mTopTouchRegionAdjustment = -newY;
415 } else {
416 mTopTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800417 }
Adam Cohen67882692011-03-11 15:29:03 -0800418 if (newY + newHeight > mDragLayer.getHeight()) {
Adam Cohen4459d6b2012-07-13 15:59:15 -0700419 // In this case we shift the touch region up to end at the bottom of the DragLayer
420 mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
421 } else {
422 mBottomTouchRegionAdjustment = 0;
Adam Cohen3cba7222011-03-02 19:03:11 -0800423 }
424
Jon Miranda26e4d172016-12-12 15:43:18 -0800425 final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
Adam Cohend4844c32011-02-18 19:25:06 -0800426 if (!animate) {
427 lp.width = newWidth;
428 lp.height = newHeight;
429 lp.x = newX;
430 lp.y = newY;
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700431 for (int i = 0; i < HANDLE_COUNT; i++) {
432 mDragHandles[i].setAlpha(1.0f);
433 }
Adam Cohend4844c32011-02-18 19:25:06 -0800434 requestLayout();
435 } else {
Sunny Goyal849c6a22018-08-08 16:33:46 -0700436 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(lp, this,
437 PropertyValuesHolder.ofInt("width", lp.width, newWidth),
438 PropertyValuesHolder.ofInt("height", lp.height, newHeight),
439 PropertyValuesHolder.ofInt("x", lp.x, newX),
440 PropertyValuesHolder.ofInt("y", lp.y, newY));
441 oa.addUpdateListener(a -> requestLayout());
442
443 AnimatorSet set = new AnimatorSet();
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700444 set.play(oa);
445 for (int i = 0; i < HANDLE_COUNT; i++) {
Sunny Goyal849c6a22018-08-08 16:33:46 -0700446 set.play(LauncherAnimUtils.ofPropertyValuesHolder(mDragHandles[i],
447 PropertyValuesHolder.ofFloat(ALPHA, 1f)));
Adam Cohend4844c32011-02-18 19:25:06 -0800448 }
449
450 set.setDuration(SNAP_DURATION);
451 set.start();
452 }
Tony Wickham71255bb2016-02-10 16:18:15 -0800453
454 setFocusableInTouchMode(true);
455 requestFocus();
456 }
457
458 @Override
459 public boolean onKey(View v, int keyCode, KeyEvent event) {
460 // Clear the frame and give focus to the widget host view when a directional key is pressed.
461 if (FocusLogic.shouldConsume(keyCode)) {
Sunny Goyal37920962017-09-28 13:43:24 -0700462 close(false);
Tony Wickham71255bb2016-02-10 16:18:15 -0800463 mWidgetView.requestFocus();
464 return true;
465 }
466 return false;
Adam Cohend4844c32011-02-18 19:25:06 -0800467 }
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700468
469 private boolean handleTouchDown(MotionEvent ev) {
470 Rect hitRect = new Rect();
471 int x = (int) ev.getX();
472 int y = (int) ev.getY();
473
474 getHitRect(hitRect);
475 if (hitRect.contains(x, y)) {
476 if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
477 mXDown = x;
478 mYDown = y;
479 return true;
480 }
481 }
482 return false;
483 }
484
485 @Override
486 public boolean onControllerTouchEvent(MotionEvent ev) {
487 int action = ev.getAction();
488 int x = (int) ev.getX();
489 int y = (int) ev.getY();
490
491 switch (action) {
492 case MotionEvent.ACTION_DOWN:
493 return handleTouchDown(ev);
494 case MotionEvent.ACTION_MOVE:
495 visualizeResizeForDelta(x - mXDown, y - mYDown);
496 break;
497 case MotionEvent.ACTION_CANCEL:
498 case MotionEvent.ACTION_UP:
499 visualizeResizeForDelta(x - mXDown, y - mYDown);
500 onTouchUp();
501 mXDown = mYDown = 0;
502 break;
503 }
504 return true;
505 }
506
507 @Override
508 public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
509 if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
510 return true;
511 }
Sunny Goyal37920962017-09-28 13:43:24 -0700512 close(false);
Sunny Goyal6ad72f02016-09-23 11:01:10 -0700513 return false;
514 }
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700515
Sunny Goyal37920962017-09-28 13:43:24 -0700516 @Override
517 protected void handleClose(boolean animate) {
518 mDragLayer.removeView(this);
519 }
520
521 @Override
522 public void logActionCommand(int command) {
523 // TODO: Log this case.
524 }
525
526 @Override
527 protected boolean isOfType(int type) {
528 return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
529 }
530
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700531 /**
532 * A mutable class for describing the range of two int values.
533 */
534 private static class IntRange {
535
536 public int start, end;
537
538 public int clamp(int value) {
539 return Utilities.boundToRange(value, start, end);
540 }
541
542 public void set(int s, int e) {
543 start = s;
544 end = e;
545 }
546
547 public int size() {
548 return end - start;
549 }
550
551 /**
552 * Moves either the start or end edge (but never both) by {@param delta} and sets the
553 * result in {@param out}
554 */
555 public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
556 out.start = moveStart ? start + delta : start;
557 out.end = moveEnd ? end + delta : end;
558 }
559
560 /**
561 * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
562 * with extra conditions.
563 * @param minSize minimum size after with the moving edge should not be shifted any further.
564 * For eg, if delta = -3 when moving the endEdge brings the size to less than
565 * minSize, only delta = -2 will applied
566 * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
567 * @return the amount of increase when endEdge was moves and the amount of decrease when
568 * the start edge was moved.
569 */
570 public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
571 int minSize, int maxEnd, IntRange out) {
572 applyDelta(moveStart, moveEnd, delta, out);
Jon Miranda9f33cb22017-01-03 09:30:00 -0800573 if (out.start < 0) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700574 out.start = 0;
575 }
Jon Miranda9f33cb22017-01-03 09:30:00 -0800576 if (out.end > maxEnd) {
Sunny Goyal08ca40f2016-09-13 21:07:31 -0700577 out.end = maxEnd;
578 }
579 if (out.size() < minSize) {
580 if (moveStart) {
581 out.start = out.end - minSize;
582 } else if (moveEnd) {
583 out.end = out.start + minSize;
584 }
585 }
586 return moveEnd ? out.size() - size() : size() - out.size();
587 }
588 }
Adam Cohend4844c32011-02-18 19:25:06 -0800589}