blob: aa3e66c095c801a96e4c5ecdc27532c0fe8f704d [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
Sunny Goyalfa401a12015-04-10 13:45:42 -070032import com.android.launcher3.R;
Adam Cohen091440a2015-03-18 14:16:05 -070033import com.android.launcher3.util.Thunk;
Hyunyoung Song3f471442015-04-08 19:01:34 -070034import com.android.launcher3.widget.WidgetsContainerView;
Kenny Guyed131872014-04-30 03:02:21 +010035
Winson Chung61fa4192011-06-12 15:15:29 -070036public class DeleteDropTarget extends ButtonDropTarget {
Sunny Goyalfa401a12015-04-10 13:45:42 -070037
Winson Chung6e1bdaf2012-05-29 17:03:45 -070038 private static int FLING_DELETE_ANIMATION_DURATION = 350;
39 private static float FLING_TO_DELETE_FRICTION = 0.035f;
Winson Chung043f2af2012-03-01 16:09:54 -080040 private static int MODE_FLING_DELETE_TO_TRASH = 0;
41 private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
Winson Chung4c98d922011-05-31 16:50:48 -070042
Winson Chung043f2af2012-03-01 16:09:54 -080043 private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
44
Winson Chung4c98d922011-05-31 16:50:48 -070045 public DeleteDropTarget(Context context, AttributeSet attrs) {
46 this(context, attrs, 0);
47 }
48
49 public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
50 super(context, attrs, defStyle);
51 }
52
53 @Override
54 protected void onFinishInflate() {
55 super.onFinishInflate();
Winson Chung4c98d922011-05-31 16:50:48 -070056 // Get the hover color
Sunny Goyalfa401a12015-04-10 13:45:42 -070057 mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
Adam Cohenebea84d2011-11-09 17:20:41 -080058
Sunny Goyalfa401a12015-04-10 13:45:42 -070059 setDrawable(R.drawable.remove_target_selector);
Winson Chung4c98d922011-05-31 16:50:48 -070060 }
61
Sunny Goyalfa401a12015-04-10 13:45:42 -070062 public static boolean willAcceptDrop(DragSource source, Object info) {
63 return (info instanceof ItemInfo) && source.supportsDeleteDropTarget();
Winson Chunga48487a2012-03-20 16:19:37 -070064 }
65
Winson Chung4c98d922011-05-31 16:50:48 -070066 @Override
Sunny Goyalfa401a12015-04-10 13:45:42 -070067 protected boolean supportsDrop(DragSource source, Object info) {
68 return willAcceptDrop(source, info);
Winson Chung4c98d922011-05-31 16:50:48 -070069 }
70
71 @Override
Adam Cohen091440a2015-03-18 14:16:05 -070072 @Thunk void completeDrop(DragObject d) {
Michael Jurka1e2f4652013-07-08 18:03:46 -070073 ItemInfo item = (ItemInfo) d.dragInfo;
Sunny Goyalfa401a12015-04-10 13:45:42 -070074 if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
Sunny Goyal71b5c0b2015-01-08 16:59:04 -080075 removeWorkspaceOrFolderItem(mLauncher, item, null);
Winson Chung4c98d922011-05-31 16:50:48 -070076 }
Sunny Goyal71b5c0b2015-01-08 16:59:04 -080077 }
78
79 /**
80 * Removes the item from the workspace. If the view is not null, it also removes the view.
81 * @return true if the item was removed.
82 */
83 public static boolean removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
84 if (item instanceof ShortcutInfo) {
85 LauncherModel.deleteItemFromDatabase(launcher, item);
86 } else if (item instanceof FolderInfo) {
87 FolderInfo folder = (FolderInfo) item;
88 launcher.removeFolder(folder);
89 LauncherModel.deleteFolderContentsFromDatabase(launcher, folder);
90 } else if (item instanceof LauncherAppWidgetInfo) {
91 final LauncherAppWidgetInfo widget = (LauncherAppWidgetInfo) item;
92
93 // Remove the widget from the workspace
94 launcher.removeAppWidget(widget);
95 LauncherModel.deleteItemFromDatabase(launcher, widget);
96
97 final LauncherAppWidgetHost appWidgetHost = launcher.getAppWidgetHost();
98
99 if (appWidgetHost != null && !widget.isCustomWidget()
100 && widget.isWidgetIdValid()) {
101 // Deleting an app widget ID is a void call but writes to disk before returning
102 // to the caller...
103 new AsyncTask<Void, Void, Void>() {
104 public Void doInBackground(Void ... args) {
105 appWidgetHost.deleteAppWidgetId(widget.appWidgetId);
106 return null;
107 }
Sunny Goyalfa401a12015-04-10 13:45:42 -0700108 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Sunny Goyal71b5c0b2015-01-08 16:59:04 -0800109 }
110 } else {
111 return false;
112 }
113
114 if (view != null) {
115 launcher.getWorkspace().removeWorkspaceItem(view);
116 launcher.getWorkspace().stripEmptyScreens();
117 }
118 return true;
119 }
120
Winson Chung043f2af2012-03-01 16:09:54 -0800121 /**
122 * Creates an animation from the current drag view to the delete trash icon.
123 */
124 private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer,
125 DragObject d, PointF vel, ViewConfiguration config) {
Adam Cohenfe9da812014-08-04 17:08:01 -0700126
Sunny Goyalfa401a12015-04-10 13:45:42 -0700127 int width = mDrawable.getIntrinsicWidth();
128 int height = mDrawable.getIntrinsicHeight();
Winson Chung043f2af2012-03-01 16:09:54 -0800129 final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
Adam Cohenfe9da812014-08-04 17:08:01 -0700130 width, height);
Winson Chung043f2af2012-03-01 16:09:54 -0800131 final Rect from = new Rect();
132 dragLayer.getViewRectRelativeToSelf(d.dragView, from);
133
134 // Calculate how far along the velocity vector we should put the intermediate point on
135 // the bezier curve
136 float velocity = Math.abs(vel.length());
137 float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f));
138 int offsetY = (int) (-from.top * vp);
139 int offsetX = (int) (offsetY / (vel.y / vel.x));
140 final float y2 = from.top + offsetY; // intermediate t/l
141 final float x2 = from.left + offsetX;
142 final float x1 = from.left; // drag view t/l
143 final float y1 = from.top;
144 final float x3 = to.left; // delete target t/l
145 final float y3 = to.top;
146
147 final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() {
148 @Override
149 public float getInterpolation(float t) {
150 return t * t * t * t * t * t * t * t;
151 }
152 };
153 return new AnimatorUpdateListener() {
154 @Override
155 public void onAnimationUpdate(ValueAnimator animation) {
156 final DragView dragView = (DragView) dragLayer.getAnimatedView();
157 float t = ((Float) animation.getAnimatedValue()).floatValue();
158 float tp = scaleAlphaInterpolator.getInterpolation(t);
159 float initialScale = dragView.getInitialScale();
160 float finalAlpha = 0.5f;
161 float scale = dragView.getScaleX();
162 float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f;
163 float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f;
164 float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) +
165 (t * t) * x3;
166 float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) +
167 (t * t) * y3;
168
169 dragView.setTranslationX(x);
170 dragView.setTranslationY(y);
171 dragView.setScaleX(initialScale * (1f - tp));
172 dragView.setScaleY(initialScale * (1f - tp));
173 dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp));
174 }
175 };
176 }
177
178 /**
179 * Creates an animation from the current drag view along its current velocity vector.
180 * For this animation, the alpha runs for a fixed duration and we update the position
181 * progressively.
182 */
183 private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
Winson Chung043f2af2012-03-01 16:09:54 -0800184 private DragLayer mDragLayer;
185 private PointF mVelocity;
186 private Rect mFrom;
187 private long mPrevTime;
188 private boolean mHasOffsetForScale;
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700189 private float mFriction;
Winson Chung043f2af2012-03-01 16:09:54 -0800190
Winson Chung9658b1e2012-04-09 18:30:07 -0700191 private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
Winson Chung043f2af2012-03-01 16:09:54 -0800192
193 public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700194 long startTime, float friction) {
Winson Chung043f2af2012-03-01 16:09:54 -0800195 mDragLayer = dragLayer;
196 mVelocity = vel;
197 mFrom = from;
198 mPrevTime = startTime;
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700199 mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction);
Winson Chung043f2af2012-03-01 16:09:54 -0800200 }
201
202 @Override
203 public void onAnimationUpdate(ValueAnimator animation) {
204 final DragView dragView = (DragView) mDragLayer.getAnimatedView();
205 float t = ((Float) animation.getAnimatedValue()).floatValue();
206 long curTime = AnimationUtils.currentAnimationTimeMillis();
207
208 if (!mHasOffsetForScale) {
209 mHasOffsetForScale = true;
210 float scale = dragView.getScaleX();
211 float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f;
212 float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f;
213
214 mFrom.left += xOffset;
215 mFrom.top += yOffset;
216 }
217
218 mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
219 mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
220
221 dragView.setTranslationX(mFrom.left);
222 dragView.setTranslationY(mFrom.top);
223 dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
224
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700225 mVelocity.x *= mFriction;
226 mVelocity.y *= mFriction;
Winson Chung043f2af2012-03-01 16:09:54 -0800227 mPrevTime = curTime;
228 }
229 };
230 private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer,
231 DragObject d, PointF vel, final long startTime, final int duration,
232 ViewConfiguration config) {
233 final Rect from = new Rect();
234 dragLayer.getViewRectRelativeToSelf(d.dragView, from);
235
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700236 return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime,
237 FLING_TO_DELETE_FRICTION);
Winson Chung043f2af2012-03-01 16:09:54 -0800238 }
239
240 public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
Hyunyoung Song3f471442015-04-08 19:01:34 -0700241 final boolean isWidgets = d.dragSource instanceof WidgetsContainerView;
242 final boolean isAllapps = d.dragSource instanceof AppsContainerView;
Winson Chunga48487a2012-03-20 16:19:37 -0700243
Winson Chung043f2af2012-03-01 16:09:54 -0800244 // Don't highlight the icon as it's animating
245 d.dragView.setColor(0);
246 d.dragView.updateInitialScaleToCurrentScale();
Winson Chunga48487a2012-03-20 16:19:37 -0700247 // Don't highlight the target if we are flinging from AllApps
Hyunyoung Song3f471442015-04-08 19:01:34 -0700248 if (isWidgets || isAllapps) {
Winson Chunga48487a2012-03-20 16:19:37 -0700249 resetHoverColor();
250 }
Winson Chung043f2af2012-03-01 16:09:54 -0800251
252 if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
253 // Defer animating out the drop target if we are animating to it
254 mSearchDropTargetBar.deferOnDragEnd();
255 mSearchDropTargetBar.finishAnimations();
256 }
257
258 final ViewConfiguration config = ViewConfiguration.get(mLauncher);
259 final DragLayer dragLayer = mLauncher.getDragLayer();
Winson Chung6e1bdaf2012-05-29 17:03:45 -0700260 final int duration = FLING_DELETE_ANIMATION_DURATION;
Winson Chung043f2af2012-03-01 16:09:54 -0800261 final long startTime = AnimationUtils.currentAnimationTimeMillis();
262
263 // NOTE: Because it takes time for the first frame of animation to actually be
264 // called and we expect the animation to be a continuation of the fling, we have
265 // to account for the time that has elapsed since the fling finished. And since
266 // we don't have a startDelay, we will always get call to update when we call
267 // start() (which we want to ignore).
268 final TimeInterpolator tInterpolator = new TimeInterpolator() {
269 private int mCount = -1;
270 private float mOffset = 0f;
271
272 @Override
273 public float getInterpolation(float t) {
274 if (mCount < 0) {
275 mCount++;
276 } else if (mCount == 0) {
277 mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
278 startTime) / duration);
279 mCount++;
280 }
281 return Math.min(1f, mOffset + t);
282 }
283 };
284 AnimatorUpdateListener updateCb = null;
285 if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
286 updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config);
287 } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) {
288 updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
289 duration, config);
290 }
Michael Jurka1e2f4652013-07-08 18:03:46 -0700291
Winson Chung043f2af2012-03-01 16:09:54 -0800292 Runnable onAnimationEndRunnable = new Runnable() {
293 @Override
294 public void run() {
Winson Chunga48487a2012-03-20 16:19:37 -0700295 // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
296 // itself, otherwise, complete the drop to initiate the deletion process
Hyunyoung Song3f471442015-04-08 19:01:34 -0700297 if (!isWidgets || !isAllapps) {
Winson Chunga48487a2012-03-20 16:19:37 -0700298 mLauncher.exitSpringLoadedDragMode();
299 completeDrop(d);
300 }
301 mLauncher.getDragController().onDeferredEndFling(d);
Winson Chung043f2af2012-03-01 16:09:54 -0800302 }
303 };
304 dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
305 DragLayer.ANIMATION_END_DISAPPEAR, null);
306 }
Winson Chung4c98d922011-05-31 16:50:48 -0700307}