blob: e741b9787ccc1417c4c4d5f22ea9ef2964fae599 [file] [log] [blame]
Winson Chung4c98d922011-05-31 16:50:48 -07001/*
2 * Copyright (C) 2011 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;
Winson Chung4c98d922011-05-31 16:50:48 -070018
Winson Chung043f2af2012-03-01 16:09:54 -080019import android.animation.TimeInterpolator;
20import android.animation.ValueAnimator;
21import android.animation.ValueAnimator.AnimatorUpdateListener;
Winson Chung4c98d922011-05-31 16:50:48 -070022import android.content.Context;
Winson Chung043f2af2012-03-01 16:09:54 -080023import android.graphics.PointF;
Adam Cohend4d7aa52011-07-19 21:47:37 -070024import android.graphics.Rect;
Michael Jurka43467462013-11-14 12:03:58 +010025import android.os.AsyncTask;
Winson Chung4c98d922011-05-31 16:50:48 -070026import android.util.AttributeSet;
27import android.view.View;
Winson Chung043f2af2012-03-01 16:09:54 -080028import android.view.ViewConfiguration;
Winson Chung043f2af2012-03-01 16:09:54 -080029import android.view.animation.AnimationUtils;
Adam Cohend4d7aa52011-07-19 21:47:37 -070030import android.view.animation.DecelerateInterpolator;
Winson Chung4c98d922011-05-31 16:50:48 -070031
Adam Cohen091440a2015-03-18 14:16:05 -070032import com.android.launcher3.util.Thunk;
Hyunyoung Song3f471442015-04-08 19:01:34 -070033import com.android.launcher3.widget.WidgetsContainerView;
Kenny Guyed131872014-04-30 03:02:21 +010034
Winson Chung61fa4192011-06-12 15:15:29 -070035public class DeleteDropTarget extends ButtonDropTarget {
Sunny Goyalfa401a12015-04-10 13:45:42 -070036
Winson Chung6e1bdaf2012-05-29 17:03:45 -070037 private static int FLING_DELETE_ANIMATION_DURATION = 350;
38 private static float FLING_TO_DELETE_FRICTION = 0.035f;
Winson Chung043f2af2012-03-01 16:09:54 -080039 private static int MODE_FLING_DELETE_TO_TRASH = 0;
40 private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
Winson Chung4c98d922011-05-31 16:50:48 -070041
Winson Chung043f2af2012-03-01 16:09:54 -080042 private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
43
Winson Chung4c98d922011-05-31 16:50:48 -070044 public DeleteDropTarget(Context context, AttributeSet attrs) {
45 this(context, attrs, 0);
46 }
47
48 public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
49 super(context, attrs, defStyle);
50 }
51
52 @Override
53 protected void onFinishInflate() {
54 super.onFinishInflate();
Winson Chung4c98d922011-05-31 16:50:48 -070055 // Get the hover color
Sunny Goyalfa401a12015-04-10 13:45:42 -070056 mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
Adam Cohenebea84d2011-11-09 17:20:41 -080057
Sunny Goyalfa401a12015-04-10 13:45:42 -070058 setDrawable(R.drawable.remove_target_selector);
Winson Chung4c98d922011-05-31 16:50:48 -070059 }
60
Sunny Goyal1a70cef2015-04-22 11:29:51 -070061 public static boolean supportsDrop(Object info) {
62 return (info instanceof ShortcutInfo)
63 || (info instanceof LauncherAppWidgetInfo)
64 || (info instanceof FolderInfo);
Winson Chunga48487a2012-03-20 16:19:37 -070065 }
66
Winson Chung4c98d922011-05-31 16:50:48 -070067 @Override
Sunny Goyalfa401a12015-04-10 13:45:42 -070068 protected boolean supportsDrop(DragSource source, Object info) {
Sunny Goyal1a70cef2015-04-22 11:29:51 -070069 return source.supportsDeleteDropTarget() && supportsDrop(info);
Winson Chung4c98d922011-05-31 16:50:48 -070070 }
71
72 @Override
Adam Cohen091440a2015-03-18 14:16:05 -070073 @Thunk void completeDrop(DragObject d) {
Michael Jurka1e2f4652013-07-08 18:03:46 -070074 ItemInfo item = (ItemInfo) d.dragInfo;
Sunny Goyalfa401a12015-04-10 13:45:42 -070075 if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
Sunny Goyal71b5c0b2015-01-08 16:59:04 -080076 removeWorkspaceOrFolderItem(mLauncher, item, null);
Winson Chung4c98d922011-05-31 16:50:48 -070077 }
Sunny Goyal71b5c0b2015-01-08 16:59:04 -080078 }
79
80 /**
81 * Removes the item from the workspace. If the view is not null, it also removes the view.
82 * @return true if the item was removed.
83 */
84 public static boolean removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
85 if (item instanceof ShortcutInfo) {
86 LauncherModel.deleteItemFromDatabase(launcher, item);
87 } else if (item instanceof FolderInfo) {
88 FolderInfo folder = (FolderInfo) item;
89 launcher.removeFolder(folder);
90 LauncherModel.deleteFolderContentsFromDatabase(launcher, folder);
91 } else if (item instanceof LauncherAppWidgetInfo) {
92 final LauncherAppWidgetInfo widget = (LauncherAppWidgetInfo) item;
93
94 // Remove the widget from the workspace
95 launcher.removeAppWidget(widget);
96 LauncherModel.deleteItemFromDatabase(launcher, widget);
97
98 final LauncherAppWidgetHost appWidgetHost = launcher.getAppWidgetHost();
99
100 if (appWidgetHost != null && !widget.isCustomWidget()
101 && widget.isWidgetIdValid()) {
102 // Deleting an app widget ID is a void call but writes to disk before returning
103 // to the caller...
104 new AsyncTask<Void, Void, Void>() {
105 public Void doInBackground(Void ... args) {
106 appWidgetHost.deleteAppWidgetId(widget.appWidgetId);
107 return null;
108 }
Sunny Goyalfa401a12015-04-10 13:45:42 -0700109 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Sunny Goyal71b5c0b2015-01-08 16:59:04 -0800110 }
111 } else {
112 return false;
113 }
114
115 if (view != null) {
116 launcher.getWorkspace().removeWorkspaceItem(view);
117 launcher.getWorkspace().stripEmptyScreens();
118 }
119 return true;
120 }
121
Winson Chung043f2af2012-03-01 16:09:54 -0800122 /**
123 * Creates an animation from the current drag view to the delete trash icon.
124 */
125 private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer,
126 DragObject d, PointF vel, ViewConfiguration config) {
Adam Cohenfe9da812014-08-04 17:08:01 -0700127
Sunny Goyalfa401a12015-04-10 13:45:42 -0700128 int width = mDrawable.getIntrinsicWidth();
129 int height = mDrawable.getIntrinsicHeight();
Winson Chung043f2af2012-03-01 16:09:54 -0800130 final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
Adam Cohenfe9da812014-08-04 17:08:01 -0700131 width, height);
Winson Chung043f2af2012-03-01 16:09:54 -0800132 final Rect from = new Rect();
133 dragLayer.getViewRectRelativeToSelf(d.dragView, from);
134
135 // Calculate how far along the velocity vector we should put the intermediate point on
136 // the bezier curve
137 float velocity = Math.abs(vel.length());
138 float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f));
139 int offsetY = (int) (-from.top * vp);
140 int offsetX = (int) (offsetY / (vel.y / vel.x));
141 final float y2 = from.top + offsetY; // intermediate t/l
142 final float x2 = from.left + offsetX;
143 final float x1 = from.left; // drag view t/l
144 final float y1 = from.top;
145 final float x3 = to.left; // delete target t/l
146 final float y3 = to.top;
147
148 final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() {
149 @Override
150 public float getInterpolation(float t) {
151 return t * t * t * t * t * t * t * t;
152 }
153 };
154 return new AnimatorUpdateListener() {
155 @Override
156 public void onAnimationUpdate(ValueAnimator animation) {
157 final DragView dragView = (DragView) dragLayer.getAnimatedView();
158 float t = ((Float) animation.getAnimatedValue()).floatValue();
159 float tp = scaleAlphaInterpolator.getInterpolation(t);
160 float initialScale = dragView.getInitialScale();
161 float finalAlpha = 0.5f;
162 float scale = dragView.getScaleX();
163 float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f;
164 float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f;
165 float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) +
166 (t * t) * x3;
167 float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) +
168 (t * t) * y3;
169
170 dragView.setTranslationX(x);
171 dragView.setTranslationY(y);
172 dragView.setScaleX(initialScale * (1f - tp));
173 dragView.setScaleY(initialScale * (1f - tp));
174 dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp));
175 }
176 };
177 }
178
179 /**
180 * Creates an animation from the current drag view along its current velocity vector.
181 * For this animation, the alpha runs for a fixed duration and we update the position
182 * progressively.
183 */
184 private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
Winson Chung043f2af2012-03-01 16:09:54 -0800185 private DragLayer mDragLayer;
186 private PointF mVelocity;
187 private Rect mFrom;
188 private long mPrevTime;
189 private boolean mHasOffsetForScale;
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700190 private float mFriction;
Winson Chung043f2af2012-03-01 16:09:54 -0800191
Winson Chung9658b1e2012-04-09 18:30:07 -0700192 private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
Winson Chung043f2af2012-03-01 16:09:54 -0800193
194 public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700195 long startTime, float friction) {
Winson Chung043f2af2012-03-01 16:09:54 -0800196 mDragLayer = dragLayer;
197 mVelocity = vel;
198 mFrom = from;
199 mPrevTime = startTime;
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700200 mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction);
Winson Chung043f2af2012-03-01 16:09:54 -0800201 }
202
203 @Override
204 public void onAnimationUpdate(ValueAnimator animation) {
205 final DragView dragView = (DragView) mDragLayer.getAnimatedView();
206 float t = ((Float) animation.getAnimatedValue()).floatValue();
207 long curTime = AnimationUtils.currentAnimationTimeMillis();
208
209 if (!mHasOffsetForScale) {
210 mHasOffsetForScale = true;
211 float scale = dragView.getScaleX();
212 float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f;
213 float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f;
214
215 mFrom.left += xOffset;
216 mFrom.top += yOffset;
217 }
218
219 mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
220 mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
221
222 dragView.setTranslationX(mFrom.left);
223 dragView.setTranslationY(mFrom.top);
224 dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
225
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700226 mVelocity.x *= mFriction;
227 mVelocity.y *= mFriction;
Winson Chung043f2af2012-03-01 16:09:54 -0800228 mPrevTime = curTime;
229 }
230 };
231 private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer,
232 DragObject d, PointF vel, final long startTime, final int duration,
233 ViewConfiguration config) {
234 final Rect from = new Rect();
235 dragLayer.getViewRectRelativeToSelf(d.dragView, from);
236
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700237 return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime,
238 FLING_TO_DELETE_FRICTION);
Winson Chung043f2af2012-03-01 16:09:54 -0800239 }
240
241 public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
Hyunyoung Song3f471442015-04-08 19:01:34 -0700242 final boolean isWidgets = d.dragSource instanceof WidgetsContainerView;
243 final boolean isAllapps = d.dragSource instanceof AppsContainerView;
Winson Chunga48487a2012-03-20 16:19:37 -0700244
Winson Chung043f2af2012-03-01 16:09:54 -0800245 // Don't highlight the icon as it's animating
246 d.dragView.setColor(0);
247 d.dragView.updateInitialScaleToCurrentScale();
Winson Chunga48487a2012-03-20 16:19:37 -0700248 // Don't highlight the target if we are flinging from AllApps
Hyunyoung Song3f471442015-04-08 19:01:34 -0700249 if (isWidgets || isAllapps) {
Winson Chunga48487a2012-03-20 16:19:37 -0700250 resetHoverColor();
251 }
Winson Chung043f2af2012-03-01 16:09:54 -0800252
253 if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
254 // Defer animating out the drop target if we are animating to it
255 mSearchDropTargetBar.deferOnDragEnd();
256 mSearchDropTargetBar.finishAnimations();
257 }
258
259 final ViewConfiguration config = ViewConfiguration.get(mLauncher);
260 final DragLayer dragLayer = mLauncher.getDragLayer();
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700261 final int duration = FLING_DELETE_ANIMATION_DURATION;
Winson Chung043f2af2012-03-01 16:09:54 -0800262 final long startTime = AnimationUtils.currentAnimationTimeMillis();
263
264 // NOTE: Because it takes time for the first frame of animation to actually be
265 // called and we expect the animation to be a continuation of the fling, we have
266 // to account for the time that has elapsed since the fling finished. And since
267 // we don't have a startDelay, we will always get call to update when we call
268 // start() (which we want to ignore).
269 final TimeInterpolator tInterpolator = new TimeInterpolator() {
270 private int mCount = -1;
271 private float mOffset = 0f;
272
273 @Override
274 public float getInterpolation(float t) {
275 if (mCount < 0) {
276 mCount++;
277 } else if (mCount == 0) {
278 mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
279 startTime) / duration);
280 mCount++;
281 }
282 return Math.min(1f, mOffset + t);
283 }
284 };
285 AnimatorUpdateListener updateCb = null;
286 if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
287 updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config);
288 } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) {
289 updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
290 duration, config);
291 }
Michael Jurka1e2f4652013-07-08 18:03:46 -0700292
Winson Chung043f2af2012-03-01 16:09:54 -0800293 Runnable onAnimationEndRunnable = new Runnable() {
294 @Override
295 public void run() {
Winson Chunga48487a2012-03-20 16:19:37 -0700296 // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
297 // itself, otherwise, complete the drop to initiate the deletion process
Hyunyoung Song3f471442015-04-08 19:01:34 -0700298 if (!isWidgets || !isAllapps) {
Winson Chunga48487a2012-03-20 16:19:37 -0700299 mLauncher.exitSpringLoadedDragMode();
300 completeDrop(d);
301 }
302 mLauncher.getDragController().onDeferredEndFling(d);
Winson Chung043f2af2012-03-01 16:09:54 -0800303 }
304 };
305 dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
306 DragLayer.ANIMATION_END_DISAPPEAR, null);
307 }
Sunny Goyal1a70cef2015-04-22 11:29:51 -0700308
309 @Override
310 protected String getAccessibilityDropConfirmation() {
311 return getResources().getString(R.string.item_removed);
312 }
Winson Chung4c98d922011-05-31 16:50:48 -0700313}