1 /*
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 
17 package com.android.launcher3;
18 
19 import android.animation.TimeInterpolator;
20 import android.animation.ValueAnimator;
21 import android.animation.ValueAnimator.AnimatorUpdateListener;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.res.ColorStateList;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.graphics.PointF;
28 import android.graphics.Rect;
29 import android.graphics.drawable.TransitionDrawable;
30 import android.os.AsyncTask;
31 import android.os.Build;
32 import android.os.Bundle;
33 import android.os.UserManager;
34 import android.util.AttributeSet;
35 import android.view.View;
36 import android.view.ViewConfiguration;
37 import android.view.ViewGroup;
38 import android.view.animation.AnimationUtils;
39 import android.view.animation.DecelerateInterpolator;
40 import android.view.animation.LinearInterpolator;
41 
42 import com.android.launcher3.compat.UserHandleCompat;
43 
44 public class DeleteDropTarget extends ButtonDropTarget {
45     private static int DELETE_ANIMATION_DURATION = 285;
46     private static int FLING_DELETE_ANIMATION_DURATION = 350;
47     private static float FLING_TO_DELETE_FRICTION = 0.035f;
48     private static int MODE_FLING_DELETE_TO_TRASH = 0;
49     private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
50 
51     private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
52 
53     private ColorStateList mOriginalTextColor;
54     private TransitionDrawable mUninstallDrawable;
55     private TransitionDrawable mRemoveDrawable;
56     private TransitionDrawable mCurrentDrawable;
57 
58     private boolean mWaitingForUninstall = false;
59 
DeleteDropTarget(Context context, AttributeSet attrs)60     public DeleteDropTarget(Context context, AttributeSet attrs) {
61         this(context, attrs, 0);
62     }
63 
DeleteDropTarget(Context context, AttributeSet attrs, int defStyle)64     public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
65         super(context, attrs, defStyle);
66     }
67 
68     @Override
onFinishInflate()69     protected void onFinishInflate() {
70         super.onFinishInflate();
71 
72         // Get the drawable
73         mOriginalTextColor = getTextColors();
74 
75         // Get the hover color
76         Resources r = getResources();
77         mHoverColor = r.getColor(R.color.delete_target_hover_tint);
78         mUninstallDrawable = (TransitionDrawable)
79                 r.getDrawable(R.drawable.uninstall_target_selector);
80         mRemoveDrawable = (TransitionDrawable) r.getDrawable(R.drawable.remove_target_selector);
81 
82         mRemoveDrawable.setCrossFadeEnabled(true);
83         mUninstallDrawable.setCrossFadeEnabled(true);
84 
85         // The current drawable is set to either the remove drawable or the uninstall drawable
86         // and is initially set to the remove drawable, as set in the layout xml.
87         mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
88 
89         // Remove the text in the Phone UI in landscape
90         int orientation = getResources().getConfiguration().orientation;
91         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
92             if (!LauncherAppState.getInstance().isScreenLarge()) {
93                 setText("");
94             }
95         }
96     }
97 
isAllAppsApplication(DragSource source, Object info)98     private boolean isAllAppsApplication(DragSource source, Object info) {
99         return source.supportsAppInfoDropTarget() && (info instanceof AppInfo);
100     }
isAllAppsWidget(DragSource source, Object info)101     private boolean isAllAppsWidget(DragSource source, Object info) {
102         if (source instanceof AppsCustomizePagedView) {
103             if (info instanceof PendingAddItemInfo) {
104                 PendingAddItemInfo addInfo = (PendingAddItemInfo) info;
105                 switch (addInfo.itemType) {
106                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
107                     case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
108                         return true;
109                 }
110             }
111         }
112         return false;
113     }
isDragSourceWorkspaceOrFolder(DragObject d)114     private boolean isDragSourceWorkspaceOrFolder(DragObject d) {
115         return (d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder);
116     }
isWorkspaceOrFolderApplication(DragObject d)117     private boolean isWorkspaceOrFolderApplication(DragObject d) {
118         return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof ShortcutInfo);
119     }
isWorkspaceOrFolderWidget(DragObject d)120     private boolean isWorkspaceOrFolderWidget(DragObject d) {
121         return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof LauncherAppWidgetInfo);
122     }
isWorkspaceFolder(DragObject d)123     private boolean isWorkspaceFolder(DragObject d) {
124         return (d.dragSource instanceof Workspace) && (d.dragInfo instanceof FolderInfo);
125     }
126 
setHoverColor()127     private void setHoverColor() {
128         if (mCurrentDrawable != null) {
129             mCurrentDrawable.startTransition(mTransitionDuration);
130         }
131         setTextColor(mHoverColor);
132     }
resetHoverColor()133     private void resetHoverColor() {
134         if (mCurrentDrawable != null) {
135             mCurrentDrawable.resetTransition();
136         }
137         setTextColor(mOriginalTextColor);
138     }
139 
140     @Override
acceptDrop(DragObject d)141     public boolean acceptDrop(DragObject d) {
142         return willAcceptDrop(d.dragInfo);
143     }
144 
willAcceptDrop(Object info)145     public static boolean willAcceptDrop(Object info) {
146         if (info instanceof ItemInfo) {
147             ItemInfo item = (ItemInfo) info;
148             if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
149                     item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
150                 return true;
151             }
152 
153             if (!LauncherAppState.isDisableAllApps() &&
154                     item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
155                 return true;
156             }
157 
158             if (!LauncherAppState.isDisableAllApps() &&
159                     item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
160                     item instanceof AppInfo) {
161                 AppInfo appInfo = (AppInfo) info;
162                 return (appInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
163             }
164 
165             if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
166                 item instanceof ShortcutInfo) {
167                 if (LauncherAppState.isDisableAllApps()) {
168                     ShortcutInfo shortcutInfo = (ShortcutInfo) info;
169                     return (shortcutInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
170                 } else {
171                     return true;
172                 }
173             }
174         }
175         return false;
176     }
177 
178     @Override
onDragStart(DragSource source, Object info, int dragAction)179     public void onDragStart(DragSource source, Object info, int dragAction) {
180         boolean isVisible = true;
181         boolean useUninstallLabel = !LauncherAppState.isDisableAllApps() &&
182                 isAllAppsApplication(source, info);
183         boolean useDeleteLabel = !useUninstallLabel && source.supportsDeleteDropTarget();
184 
185         // If we are dragging an application from AppsCustomize, only show the control if we can
186         // delete the app (it was downloaded), and rename the string to "uninstall" in such a case.
187         // Hide the delete target if it is a widget from AppsCustomize.
188         if (!willAcceptDrop(info) || isAllAppsWidget(source, info)) {
189             isVisible = false;
190         }
191         if (useUninstallLabel) {
192             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
193                 UserManager userManager = (UserManager)
194                         getContext().getSystemService(Context.USER_SERVICE);
195                 Bundle restrictions = userManager.getUserRestrictions();
196                 if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
197                         || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
198                     isVisible = false;
199                 }
200             }
201         }
202 
203         if (useUninstallLabel) {
204             setCompoundDrawablesRelativeWithIntrinsicBounds(mUninstallDrawable, null, null, null);
205         } else if (useDeleteLabel) {
206             setCompoundDrawablesRelativeWithIntrinsicBounds(mRemoveDrawable, null, null, null);
207         } else {
208             isVisible = false;
209         }
210         mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
211 
212         mActive = isVisible;
213         resetHoverColor();
214         ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
215         if (isVisible && getText().length() > 0) {
216             setText(useUninstallLabel ? R.string.delete_target_uninstall_label
217                 : R.string.delete_target_label);
218         }
219     }
220 
221     @Override
onDragEnd()222     public void onDragEnd() {
223         super.onDragEnd();
224         mActive = false;
225     }
226 
onDragEnter(DragObject d)227     public void onDragEnter(DragObject d) {
228         super.onDragEnter(d);
229 
230         setHoverColor();
231     }
232 
onDragExit(DragObject d)233     public void onDragExit(DragObject d) {
234         super.onDragExit(d);
235 
236         if (!d.dragComplete) {
237             resetHoverColor();
238         } else {
239             // Restore the hover color if we are deleting
240             d.dragView.setColor(mHoverColor);
241         }
242     }
243 
animateToTrashAndCompleteDrop(final DragObject d)244     private void animateToTrashAndCompleteDrop(final DragObject d) {
245         final DragLayer dragLayer = mLauncher.getDragLayer();
246         final Rect from = new Rect();
247         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
248 
249         int width = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicWidth();
250         int height = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicHeight();
251         final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
252                 width, height);
253         final float scale = (float) to.width() / from.width();
254 
255         mSearchDropTargetBar.deferOnDragEnd();
256         deferCompleteDropIfUninstalling(d);
257 
258         Runnable onAnimationEndRunnable = new Runnable() {
259             @Override
260             public void run() {
261                 completeDrop(d);
262                 mSearchDropTargetBar.onDragEnd();
263                 mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
264             }
265         };
266         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
267                 DELETE_ANIMATION_DURATION, new DecelerateInterpolator(2),
268                 new LinearInterpolator(), onAnimationEndRunnable,
269                 DragLayer.ANIMATION_END_DISAPPEAR, null);
270     }
271 
deferCompleteDropIfUninstalling(DragObject d)272     private void deferCompleteDropIfUninstalling(DragObject d) {
273         mWaitingForUninstall = false;
274         if (isUninstallFromWorkspace(d)) {
275             if (d.dragSource instanceof Folder) {
276                 ((Folder) d.dragSource).deferCompleteDropAfterUninstallActivity();
277             } else if (d.dragSource instanceof Workspace) {
278                 ((Workspace) d.dragSource).deferCompleteDropAfterUninstallActivity();
279             }
280             mWaitingForUninstall = true;
281         }
282     }
283 
isUninstallFromWorkspace(DragObject d)284     private boolean isUninstallFromWorkspace(DragObject d) {
285         if (LauncherAppState.isDisableAllApps() && isWorkspaceOrFolderApplication(d)) {
286             ShortcutInfo shortcut = (ShortcutInfo) d.dragInfo;
287             // Only allow manifest shortcuts to initiate an un-install.
288             return !InstallShortcutReceiver.isValidShortcutLaunchIntent(shortcut.intent);
289         }
290         return false;
291     }
292 
completeDrop(DragObject d)293     private void completeDrop(DragObject d) {
294         ItemInfo item = (ItemInfo) d.dragInfo;
295         boolean wasWaitingForUninstall = mWaitingForUninstall;
296         mWaitingForUninstall = false;
297         if (isAllAppsApplication(d.dragSource, item)) {
298             // Uninstall the application if it is being dragged from AppsCustomize
299             AppInfo appInfo = (AppInfo) item;
300             mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags,
301                     appInfo.user);
302         } else if (isUninstallFromWorkspace(d)) {
303             ShortcutInfo shortcut = (ShortcutInfo) item;
304             if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
305                 final ComponentName componentName = shortcut.intent.getComponent();
306                 final DragSource dragSource = d.dragSource;
307                 final UserHandleCompat user = shortcut.user;
308                 mWaitingForUninstall = mLauncher.startApplicationUninstallActivity(
309                         componentName, shortcut.flags, user);
310                 if (mWaitingForUninstall) {
311                     final Runnable checkIfUninstallWasSuccess = new Runnable() {
312                         @Override
313                         public void run() {
314                             mWaitingForUninstall = false;
315                             String packageName = componentName.getPackageName();
316                             boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
317                                     getContext(), packageName, user);
318                             if (dragSource instanceof Folder) {
319                                 ((Folder) dragSource).
320                                     onUninstallActivityReturned(uninstallSuccessful);
321                             } else if (dragSource instanceof Workspace) {
322                                 ((Workspace) dragSource).
323                                     onUninstallActivityReturned(uninstallSuccessful);
324                             }
325                         }
326                     };
327                     mLauncher.addOnResumeCallback(checkIfUninstallWasSuccess);
328                 }
329             }
330         } else if (isWorkspaceOrFolderApplication(d)) {
331             LauncherModel.deleteItemFromDatabase(mLauncher, item);
332         } else if (isWorkspaceFolder(d)) {
333             // Remove the folder from the workspace and delete the contents from launcher model
334             FolderInfo folderInfo = (FolderInfo) item;
335             mLauncher.removeFolder(folderInfo);
336             LauncherModel.deleteFolderContentsFromDatabase(mLauncher, folderInfo);
337         } else if (isWorkspaceOrFolderWidget(d)) {
338             // Remove the widget from the workspace
339             mLauncher.removeAppWidget((LauncherAppWidgetInfo) item);
340             LauncherModel.deleteItemFromDatabase(mLauncher, item);
341 
342             final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item;
343             final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost();
344             if ((appWidgetHost != null) && launcherAppWidgetInfo.isWidgetIdValid()) {
345                 // Deleting an app widget ID is a void call but writes to disk before returning
346                 // to the caller...
347                 new AsyncTask<Void, Void, Void>() {
348                     public Void doInBackground(Void ... args) {
349                         appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId);
350                         return null;
351                     }
352                 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
353             }
354         }
355         if (wasWaitingForUninstall && !mWaitingForUninstall) {
356             if (d.dragSource instanceof Folder) {
357                 ((Folder) d.dragSource).onUninstallActivityReturned(false);
358             } else if (d.dragSource instanceof Workspace) {
359                 ((Workspace) d.dragSource).onUninstallActivityReturned(false);
360             }
361         }
362     }
363 
onDrop(DragObject d)364     public void onDrop(DragObject d) {
365         animateToTrashAndCompleteDrop(d);
366     }
367 
368     /**
369      * Creates an animation from the current drag view to the delete trash icon.
370      */
createFlingToTrashAnimatorListener(final DragLayer dragLayer, DragObject d, PointF vel, ViewConfiguration config)371     private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer,
372             DragObject d, PointF vel, ViewConfiguration config) {
373 
374         int width = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicWidth();
375         int height = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicHeight();
376         final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
377                 width, height);
378         final Rect from = new Rect();
379         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
380 
381         // Calculate how far along the velocity vector we should put the intermediate point on
382         // the bezier curve
383         float velocity = Math.abs(vel.length());
384         float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f));
385         int offsetY = (int) (-from.top * vp);
386         int offsetX = (int) (offsetY / (vel.y / vel.x));
387         final float y2 = from.top + offsetY;                        // intermediate t/l
388         final float x2 = from.left + offsetX;
389         final float x1 = from.left;                                 // drag view t/l
390         final float y1 = from.top;
391         final float x3 = to.left;                                   // delete target t/l
392         final float y3 = to.top;
393 
394         final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() {
395             @Override
396             public float getInterpolation(float t) {
397                 return t * t * t * t * t * t * t * t;
398             }
399         };
400         return new AnimatorUpdateListener() {
401             @Override
402             public void onAnimationUpdate(ValueAnimator animation) {
403                 final DragView dragView = (DragView) dragLayer.getAnimatedView();
404                 float t = ((Float) animation.getAnimatedValue()).floatValue();
405                 float tp = scaleAlphaInterpolator.getInterpolation(t);
406                 float initialScale = dragView.getInitialScale();
407                 float finalAlpha = 0.5f;
408                 float scale = dragView.getScaleX();
409                 float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f;
410                 float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f;
411                 float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) +
412                         (t * t) * x3;
413                 float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) +
414                         (t * t) * y3;
415 
416                 dragView.setTranslationX(x);
417                 dragView.setTranslationY(y);
418                 dragView.setScaleX(initialScale * (1f - tp));
419                 dragView.setScaleY(initialScale * (1f - tp));
420                 dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp));
421             }
422         };
423     }
424 
425     /**
426      * Creates an animation from the current drag view along its current velocity vector.
427      * For this animation, the alpha runs for a fixed duration and we update the position
428      * progressively.
429      */
430     private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
431         private DragLayer mDragLayer;
432         private PointF mVelocity;
433         private Rect mFrom;
434         private long mPrevTime;
435         private boolean mHasOffsetForScale;
436         private float mFriction;
437 
438         private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
439 
440         public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
441                 long startTime, float friction) {
442             mDragLayer = dragLayer;
443             mVelocity = vel;
444             mFrom = from;
445             mPrevTime = startTime;
446             mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction);
447         }
448 
449         @Override
450         public void onAnimationUpdate(ValueAnimator animation) {
451             final DragView dragView = (DragView) mDragLayer.getAnimatedView();
452             float t = ((Float) animation.getAnimatedValue()).floatValue();
453             long curTime = AnimationUtils.currentAnimationTimeMillis();
454 
455             if (!mHasOffsetForScale) {
456                 mHasOffsetForScale = true;
457                 float scale = dragView.getScaleX();
458                 float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f;
459                 float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f;
460 
461                 mFrom.left += xOffset;
462                 mFrom.top += yOffset;
463             }
464 
465             mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
466             mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
467 
468             dragView.setTranslationX(mFrom.left);
469             dragView.setTranslationY(mFrom.top);
470             dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
471 
472             mVelocity.x *= mFriction;
473             mVelocity.y *= mFriction;
474             mPrevTime = curTime;
475         }
476     };
477     private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer,
478             DragObject d, PointF vel, final long startTime, final int duration,
479             ViewConfiguration config) {
480         final Rect from = new Rect();
481         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
482 
483         return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime,
484                 FLING_TO_DELETE_FRICTION);
485     }
486 
487     public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
488         final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView;
489 
490         // Don't highlight the icon as it's animating
491         d.dragView.setColor(0);
492         d.dragView.updateInitialScaleToCurrentScale();
493         // Don't highlight the target if we are flinging from AllApps
494         if (isAllApps) {
495             resetHoverColor();
496         }
497 
498         if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
499             // Defer animating out the drop target if we are animating to it
500             mSearchDropTargetBar.deferOnDragEnd();
501             mSearchDropTargetBar.finishAnimations();
502         }
503 
504         final ViewConfiguration config = ViewConfiguration.get(mLauncher);
505         final DragLayer dragLayer = mLauncher.getDragLayer();
506         final int duration = FLING_DELETE_ANIMATION_DURATION;
507         final long startTime = AnimationUtils.currentAnimationTimeMillis();
508 
509         // NOTE: Because it takes time for the first frame of animation to actually be
510         // called and we expect the animation to be a continuation of the fling, we have
511         // to account for the time that has elapsed since the fling finished.  And since
512         // we don't have a startDelay, we will always get call to update when we call
513         // start() (which we want to ignore).
514         final TimeInterpolator tInterpolator = new TimeInterpolator() {
515             private int mCount = -1;
516             private float mOffset = 0f;
517 
518             @Override
519             public float getInterpolation(float t) {
520                 if (mCount < 0) {
521                     mCount++;
522                 } else if (mCount == 0) {
523                     mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
524                             startTime) / duration);
525                     mCount++;
526                 }
527                 return Math.min(1f, mOffset + t);
528             }
529         };
530         AnimatorUpdateListener updateCb = null;
531         if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
532             updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config);
533         } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) {
534             updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
535                     duration, config);
536         }
537         deferCompleteDropIfUninstalling(d);
538 
539         Runnable onAnimationEndRunnable = new Runnable() {
540             @Override
541             public void run() {
542                 // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
543                 // itself, otherwise, complete the drop to initiate the deletion process
544                 if (!isAllApps) {
545                     mLauncher.exitSpringLoadedDragMode();
546                     completeDrop(d);
547                 }
548                 mLauncher.getDragController().onDeferredEndFling(d);
549             }
550         };
551         dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
552                 DragLayer.ANIMATION_END_DISAPPEAR, null);
553     }
554 }
555