• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.launcher3.taskbar;
17 
18 import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS;
19 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
20 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
21 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
22 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.ValueAnimator;
27 import android.annotation.NonNull;
28 import android.content.ClipData;
29 import android.content.ClipDescription;
30 import android.content.Intent;
31 import android.content.pm.LauncherApps;
32 import android.content.res.Resources;
33 import android.graphics.Canvas;
34 import android.graphics.Point;
35 import android.graphics.Rect;
36 import android.graphics.RectF;
37 import android.graphics.drawable.Drawable;
38 import android.os.UserHandle;
39 import android.util.Pair;
40 import android.view.DragEvent;
41 import android.view.MotionEvent;
42 import android.view.SurfaceControl;
43 import android.view.View;
44 import android.view.ViewRootImpl;
45 import android.window.SurfaceSyncer;
46 
47 import androidx.annotation.Nullable;
48 
49 import com.android.internal.logging.InstanceId;
50 import com.android.launcher3.AbstractFloatingView;
51 import com.android.launcher3.BubbleTextView;
52 import com.android.launcher3.DragSource;
53 import com.android.launcher3.DropTarget;
54 import com.android.launcher3.LauncherSettings;
55 import com.android.launcher3.R;
56 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
57 import com.android.launcher3.anim.Interpolators;
58 import com.android.launcher3.config.FeatureFlags;
59 import com.android.launcher3.dragndrop.DragController;
60 import com.android.launcher3.dragndrop.DragDriver;
61 import com.android.launcher3.dragndrop.DragOptions;
62 import com.android.launcher3.dragndrop.DragView;
63 import com.android.launcher3.dragndrop.DraggableView;
64 import com.android.launcher3.graphics.DragPreviewProvider;
65 import com.android.launcher3.logging.StatsLogManager;
66 import com.android.launcher3.model.data.ItemInfo;
67 import com.android.launcher3.model.data.WorkspaceItemInfo;
68 import com.android.launcher3.popup.PopupContainerWithArrow;
69 import com.android.launcher3.shortcuts.DeepShortcutView;
70 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
71 import com.android.launcher3.testing.TestLogging;
72 import com.android.launcher3.testing.shared.TestProtocol;
73 import com.android.launcher3.util.DisplayController;
74 import com.android.launcher3.util.IntSet;
75 import com.android.launcher3.util.ItemInfoMatcher;
76 import com.android.quickstep.util.LogUtils;
77 import com.android.quickstep.util.MultiValueUpdateListener;
78 import com.android.systemui.shared.recents.model.Task;
79 import com.android.wm.shell.draganddrop.DragAndDropConstants;
80 
81 import java.io.PrintWriter;
82 import java.util.Arrays;
83 import java.util.Collections;
84 import java.util.function.Predicate;
85 
86 /**
87  * Handles long click on Taskbar items to start a system drag and drop operation.
88  */
89 public class TaskbarDragController extends DragController<BaseTaskbarContext> implements
90         TaskbarControllers.LoggableTaskbarController {
91 
92     private static final boolean DEBUG_DRAG_SHADOW_SURFACE = false;
93     private static final int ANIM_DURATION_RETURN_ICON_TO_TASKBAR = 300;
94 
95     private final int mDragIconSize;
96     private final int[] mTempXY = new int[2];
97 
98     // Initialized in init.
99     TaskbarControllers mControllers;
100 
101     // Where the initial touch was relative to the dragged icon.
102     private int mRegistrationX;
103     private int mRegistrationY;
104 
105     private boolean mIsSystemDragInProgress;
106 
107     // Animation for the drag shadow back into position after an unsuccessful drag
108     private ValueAnimator mReturnAnimator;
109     private boolean mDisallowGlobalDrag;
110     private boolean mDisallowLongClick;
111 
TaskbarDragController(BaseTaskbarContext activity)112     public TaskbarDragController(BaseTaskbarContext activity) {
113         super(activity);
114         Resources resources = mActivity.getResources();
115         mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
116     }
117 
init(TaskbarControllers controllers)118     public void init(TaskbarControllers controllers) {
119         mControllers = controllers;
120     }
121 
setDisallowGlobalDrag(boolean disallowGlobalDrag)122     public void setDisallowGlobalDrag(boolean disallowGlobalDrag) {
123         mDisallowGlobalDrag = disallowGlobalDrag;
124     }
125 
setDisallowLongClick(boolean disallowLongClick)126     public void setDisallowLongClick(boolean disallowLongClick) {
127         mDisallowLongClick = disallowLongClick;
128     }
129 
130     /**
131      * Attempts to start a system drag and drop operation for the given View, using its tag to
132      * generate the ClipDescription and Intent.
133      * @return Whether {@link View#startDragAndDrop} started successfully.
134      */
startDragOnLongClick(View view)135     public boolean startDragOnLongClick(View view) {
136         return startDragOnLongClick(view, null, null);
137     }
138 
startDragOnLongClick( DeepShortcutView shortcutView, Point iconShift)139     protected boolean startDragOnLongClick(
140             DeepShortcutView shortcutView, Point iconShift) {
141         return startDragOnLongClick(
142                 shortcutView.getBubbleText(),
143                 new ShortcutDragPreviewProvider(shortcutView.getIconView(), iconShift),
144                 iconShift);
145     }
146 
startDragOnLongClick( View view, @Nullable DragPreviewProvider dragPreviewProvider, @Nullable Point iconShift)147     private boolean startDragOnLongClick(
148             View view,
149             @Nullable DragPreviewProvider dragPreviewProvider,
150             @Nullable Point iconShift) {
151         if (!(view instanceof BubbleTextView) || mDisallowLongClick) {
152             return false;
153         }
154         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onTaskbarItemLongClick");
155         BubbleTextView btv = (BubbleTextView) view;
156         mActivity.onDragStart();
157         btv.post(() -> {
158             DragView dragView = startInternalDrag(btv, dragPreviewProvider);
159             if (iconShift != null) {
160                 dragView.animateShift(-iconShift.x, -iconShift.y);
161             }
162             btv.setIconDisabled(true);
163             mControllers.taskbarAutohideSuspendController.updateFlag(
164                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true);
165         });
166         return true;
167     }
168 
startInternalDrag( BubbleTextView btv, @Nullable DragPreviewProvider dragPreviewProvider)169     private DragView startInternalDrag(
170             BubbleTextView btv, @Nullable DragPreviewProvider dragPreviewProvider) {
171         float iconScale = btv.getIcon().getAnimatedScale();
172 
173         // Clear the pressed state if necessary
174         btv.clearFocus();
175         btv.setPressed(false);
176         btv.clearPressedBackground();
177 
178         final DragPreviewProvider previewProvider = dragPreviewProvider == null
179                 ? new DragPreviewProvider(btv) : dragPreviewProvider;
180         final Drawable drawable = previewProvider.createDrawable();
181         final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
182         int dragLayerX = mTempXY[0];
183         int dragLayerY = mTempXY[1];
184 
185         Rect dragRect = new Rect();
186         btv.getSourceVisualDragBounds(dragRect);
187         dragLayerY += dragRect.top;
188 
189         DragOptions dragOptions = new DragOptions();
190         dragOptions.preDragCondition = null;
191         if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) {
192             PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
193                     mControllers.taskbarPopupController.showForIcon(btv);
194             if (popupContainer != null) {
195                 dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
196             }
197         }
198         if (dragOptions.preDragCondition == null) {
199             dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
200                 private DragView mDragView;
201 
202                 @Override
203                 public boolean shouldStartDrag(double distanceDragged) {
204                     return mDragView != null && mDragView.isAnimationFinished();
205                 }
206 
207                 @Override
208                 public void onPreDragStart(DropTarget.DragObject dragObject) {
209                     mDragView = dragObject.dragView;
210 
211                     if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()
212                             && !shouldStartDrag(0)) {
213                         mDragView.setOnAnimationEndCallback(() -> {
214                             // Drag might be cancelled during the DragView animation, so check
215                             // mIsPreDrag again.
216                             if (mIsInPreDrag) {
217                                 callOnDragStart();
218                             }
219                         });
220                     }
221                 }
222 
223                 @Override
224                 public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
225                     mDragView = null;
226                 }
227             };
228         }
229 
230         return startDrag(
231                 drawable,
232                 /* view = */ null,
233                 /* originalView = */ btv,
234                 dragLayerX,
235                 dragLayerY,
236                 (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
237                 (ItemInfo) btv.getTag(),
238                 /* dragVisualizeOffset = */ null,
239                 dragRect,
240                 scale * iconScale,
241                 scale,
242                 dragOptions);
243     }
244 
245     @Override
startDrag(@ullable Drawable drawable, @Nullable View view, DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options)246     protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
247             DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
248             ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
249             float dragViewScaleOnDrop, DragOptions options) {
250         mOptions = options;
251 
252         mRegistrationX = mMotionDown.x - dragLayerX;
253         mRegistrationY = mMotionDown.y - dragLayerY;
254 
255         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
256         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
257 
258         mLastDropTarget = null;
259 
260         mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
261         mDragObject.originalView = originalView;
262         mDragObject.deferDragViewCleanupPostAnimation = false;
263 
264         mIsInPreDrag = mOptions.preDragCondition != null
265                 && !mOptions.preDragCondition.shouldStartDrag(0);
266 
267         float scalePx = mDragIconSize - dragRegion.width();
268         final DragView dragView = mDragObject.dragView = new TaskbarDragView(
269                 mActivity,
270                 drawable,
271                 mRegistrationX,
272                 mRegistrationY,
273                 initialDragViewScale,
274                 dragViewScaleOnDrop,
275                 scalePx);
276         dragView.setItemInfo(dragInfo);
277         mDragObject.dragComplete = false;
278 
279         mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
280         mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
281 
282         mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
283         if (!mOptions.isAccessibleDrag) {
284             mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
285         }
286 
287         mDragObject.dragSource = source;
288         mDragObject.dragInfo = dragInfo;
289         mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
290 
291         if (dragRegion != null) {
292             dragView.setDragRegion(new Rect(dragRegion));
293         }
294 
295         dragView.show(mLastTouch.x, mLastTouch.y);
296         mDistanceSinceScroll = 0;
297 
298         if (!mIsInPreDrag) {
299             callOnDragStart();
300         } else if (mOptions.preDragCondition != null) {
301             mOptions.preDragCondition.onPreDragStart(mDragObject);
302         }
303 
304         handleMoveEvent(mLastTouch.x, mLastTouch.y);
305 
306         return dragView;
307     }
308 
309     @Override
callOnDragStart()310     protected void callOnDragStart() {
311         super.callOnDragStart();
312         // Pre-drag has ended, start the global system drag.
313         if (mDisallowGlobalDrag) {
314             AbstractFloatingView.closeAllOpenViewsExcept(mActivity, TYPE_TASKBAR_ALL_APPS);
315         } else {
316             AbstractFloatingView.closeAllOpenViews(mActivity);
317         }
318 
319         startSystemDrag((BubbleTextView) mDragObject.originalView);
320     }
321 
startSystemDrag(BubbleTextView btv)322     private void startSystemDrag(BubbleTextView btv) {
323         if (mDisallowGlobalDrag) return;
324         View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) {
325 
326             @Override
327             public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
328                 int iconSize = Math.max(mDragIconSize, btv.getWidth());
329                 shadowSize.set(iconSize, iconSize);
330                 // The registration point was taken before the icon scaled to mDragIconSize, so
331                 // offset the registration to where the touch is on the new size.
332                 int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2;
333                 int offsetY = (mDragIconSize - mDragObject.dragView.getDragRegionHeight()) / 2;
334                 shadowTouchPoint.set(mRegistrationX + offsetX, mRegistrationY + offsetY);
335             }
336 
337             @Override
338             public void onDrawShadow(Canvas canvas) {
339                 canvas.save();
340                 if (DEBUG_DRAG_SHADOW_SURFACE) {
341                     canvas.drawColor(0xffff0000);
342                 }
343                 float scale = mDragObject.dragView.getEndScale();
344                 canvas.scale(scale, scale);
345                 mDragObject.dragView.draw(canvas);
346                 canvas.restore();
347             }
348         };
349 
350         Object tag = btv.getTag();
351         ClipDescription clipDescription = null;
352         Intent intent = null;
353         if (tag instanceof ItemInfo) {
354             ItemInfo item = (ItemInfo) tag;
355             LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class);
356             clipDescription = new ClipDescription(item.title,
357                     new String[] {
358                             item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
359                                     ? ClipDescription.MIMETYPE_APPLICATION_SHORTCUT
360                                     : ClipDescription.MIMETYPE_APPLICATION_ACTIVITY
361                     });
362             intent = new Intent();
363             if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
364                 String deepShortcutId = ((WorkspaceItemInfo) item).getDeepShortcutId();
365                 intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
366                         launcherApps.getShortcutIntent(
367                                 item.getIntent().getPackage(),
368                                 deepShortcutId,
369                                 null,
370                                 item.user));
371                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
372                 intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId);
373             } else {
374                 intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
375                         launcherApps.getMainActivityLaunchIntent(item.getIntent().getComponent(),
376                                 null, item.user));
377             }
378             intent.putExtra(Intent.EXTRA_USER, item.user);
379         } else if (tag instanceof Task) {
380             Task task = (Task) tag;
381             clipDescription = new ClipDescription(task.titleDescription,
382                     new String[] {
383                             ClipDescription.MIMETYPE_APPLICATION_TASK
384                     });
385             intent = new Intent();
386             intent.putExtra(Intent.EXTRA_TASK_ID, task.key.id);
387             intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId));
388         }
389 
390         if (clipDescription != null && intent != null) {
391             Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
392                     LogUtils.getShellShareableInstanceId();
393             // Need to share the same InstanceId between launcher3 and WM Shell (internal).
394             InstanceId internalInstanceId = instanceIds.first;
395             com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second;
396 
397             intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
398             if (DisplayController.isTransientTaskbar(mActivity)) {
399                 // Tell WM Shell to ignore drag events in the provided transient taskbar region.
400                 TaskbarDragLayer dragLayer = mControllers.taskbarActivityContext.getDragLayer();
401                 int[] locationOnScreen = dragLayer.getLocationOnScreen();
402                 RectF disallowExternalDropRegion = new RectF(dragLayer.getLastDrawnTransientRect());
403                 disallowExternalDropRegion.offset(locationOnScreen[0], locationOnScreen[1]);
404                 intent.putExtra(DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION,
405                         disallowExternalDropRegion);
406             }
407 
408             ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
409             if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
410                     View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE
411                             | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION)) {
412                 onSystemDragStarted(btv);
413 
414                 mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
415                         .withInstanceId(launcherInstanceId)
416                         .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
417             }
418         }
419     }
420 
onSystemDragStarted(BubbleTextView btv)421     private void onSystemDragStarted(BubbleTextView btv) {
422         mIsSystemDragInProgress = true;
423         mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> {
424             switch (dragEvent.getAction()) {
425                 case DragEvent.ACTION_DRAG_STARTED:
426                     // Return true to tell system we are interested in events, so we get DRAG_ENDED.
427                     return true;
428                 case DragEvent.ACTION_DRAG_ENDED:
429                     mIsSystemDragInProgress = false;
430                     if (dragEvent.getResult()) {
431                         maybeOnDragEnd();
432                     } else {
433                         // This will take care of calling maybeOnDragEnd() after the animation
434                         animateGlobalDragViewToOriginalPosition(btv, dragEvent);
435                     }
436                     mActivity.getDragLayer().setOnDragListener(null);
437 
438                     return true;
439             }
440             return false;
441         });
442     }
443 
444     @Override
isDragging()445     public boolean isDragging() {
446         return super.isDragging() || mIsSystemDragInProgress;
447     }
448 
449     /** {@code true} if the system is currently handling the drag. */
isSystemDragInProgress()450     public boolean isSystemDragInProgress() {
451         return mIsSystemDragInProgress;
452     }
453 
maybeOnDragEnd()454     private void maybeOnDragEnd() {
455         if (!isDragging()) {
456             ((BubbleTextView) mDragObject.originalView).setIconDisabled(false);
457             mControllers.taskbarAutohideSuspendController.updateFlag(
458                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
459             mActivity.onDragEnd();
460             if (mReturnAnimator == null) {
461                 // Upon successful drag, immediately stash taskbar.
462                 // Note, this must be done last to ensure no AutohideSuspendFlags are active, as
463                 // that will prevent us from stashing until the timeout.
464                 mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
465             }
466         }
467     }
468 
469     @Override
endDrag()470     protected void endDrag() {
471         if (mDisallowGlobalDrag) {
472             // We need to explicitly set deferDragViewCleanupPostAnimation to true here so the
473             // super call doesn't remove it from the drag layer before the animation completes.
474             // This variable gets set in to false in super.dispatchDropComplete() because it
475             // (rightfully so, perhaps) thinks this drag operation has failed, and does its own
476             // internal cleanup.
477             // Another way to approach this would be to make all of overview a drop target and
478             // accept the drop as successful and then run the setupReturnDragAnimator to simulate
479             // drop failure to the user
480             mDragObject.deferDragViewCleanupPostAnimation = true;
481 
482             float fromX = mDragObject.x - mDragObject.xOffset;
483             float fromY = mDragObject.y - mDragObject.yOffset;
484             DragView dragView = mDragObject.dragView;
485             setupReturnDragAnimator(fromX, fromY, (View) mDragObject.originalView,
486                     (x, y, scale, alpha) -> {
487                         dragView.setTranslationX(x);
488                         dragView.setTranslationY(y);
489                         dragView.setScaleX(scale);
490                         dragView.setScaleY(scale);
491                         dragView.setAlpha(alpha);
492                     });
493             mReturnAnimator.addListener(new AnimatorListenerAdapter() {
494                 @Override
495                 public void onAnimationEnd(Animator animation) {
496                     callOnDragEnd();
497                     dragView.remove();
498                     dragView.clearAnimation();
499                     // Do this after callOnDragEnd(), because we use mReturnAnimator != null to
500                     // imply the drag was canceled rather than successful.
501                     mReturnAnimator = null;
502                 }
503             });
504             mReturnAnimator.start();
505         }
506         super.endDrag();
507     }
508 
509     @Override
callOnDragEnd()510     protected void callOnDragEnd() {
511         super.callOnDragEnd();
512         maybeOnDragEnd();
513     }
514 
animateGlobalDragViewToOriginalPosition(BubbleTextView btv, DragEvent dragEvent)515     private void animateGlobalDragViewToOriginalPosition(BubbleTextView btv,
516             DragEvent dragEvent) {
517         SurfaceControl dragSurface = dragEvent.getDragSurface();
518 
519         // For top level icons, the target is the icon itself
520         View target = findTaskbarTargetForIconView(btv);
521 
522         float fromX = dragEvent.getX() - dragEvent.getOffsetX();
523         float fromY = dragEvent.getY() - dragEvent.getOffsetY();
524         final ViewRootImpl viewRoot = target.getViewRootImpl();
525         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
526         setupReturnDragAnimator(fromX, fromY, btv,
527                 (x, y, scale, alpha) -> {
528                     tx.setPosition(dragSurface, x, y);
529                     tx.setScale(dragSurface, scale, scale);
530                     tx.setAlpha(dragSurface, alpha);
531                     tx.apply();
532                 });
533 
534         mReturnAnimator.addListener(new AnimatorListenerAdapter() {
535             private boolean mCanceled = false;
536 
537             @Override
538             public void onAnimationCancel(Animator animation) {
539                 cleanUpSurface();
540                 mCanceled = true;
541             }
542 
543             @Override
544             public void onAnimationEnd(Animator animation) {
545                 if (mCanceled) {
546                     return;
547                 }
548                 cleanUpSurface();
549             }
550 
551             private void cleanUpSurface() {
552                 tx.close();
553                 maybeOnDragEnd();
554                 // Synchronize removing the drag surface with the next draw after calling
555                 // maybeOnDragEnd()
556                 SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
557                 transaction.remove(dragSurface);
558                 SurfaceSyncer syncer = new SurfaceSyncer();
559                 int syncId = syncer.setupSync(transaction::close);
560                 syncer.addToSync(syncId, viewRoot.getView());
561                 syncer.addTransactionToSync(syncId, transaction);
562                 syncer.markSyncReady(syncId);
563                 // Do this after maybeOnDragEnd(), because we use mReturnAnimator != null to imply
564                 // the drag was canceled rather than successful.
565                 mReturnAnimator = null;
566             }
567         });
568         mReturnAnimator.start();
569     }
570 
findTaskbarTargetForIconView(@onNull View iconView)571     private View findTaskbarTargetForIconView(@NonNull View iconView) {
572         Object tag = iconView.getTag();
573         TaskbarViewController taskbarViewController = mControllers.taskbarViewController;
574 
575         if (tag instanceof ItemInfo) {
576             ItemInfo item = (ItemInfo) tag;
577             if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
578                 if (mDisallowGlobalDrag) {
579                     // We're dragging in taskbarAllApps, we don't have folders or shortcuts
580                     return iconView;
581                 }
582                 // Since all apps closes when the drag starts, target the all apps button instead.
583                 return taskbarViewController.getAllAppsButtonView();
584             } else if (item.container >= 0) {
585                 // Since folders close when the drag starts, target the folder icon instead.
586                 Predicate<ItemInfo> matcher = ItemInfoMatcher.forFolderMatch(
587                         ItemInfoMatcher.ofItemIds(IntSet.wrap(item.id)));
588                 return taskbarViewController.getFirstIconMatch(matcher);
589             } else if (item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
590                 // Find first icon with same package/user as the deep shortcut.
591                 Predicate<ItemInfo> packageUserMatcher = ItemInfoMatcher.ofPackages(
592                         Collections.singleton(item.getTargetPackage()), item.user);
593                 return taskbarViewController.getFirstIconMatch(packageUserMatcher);
594             }
595         }
596         return iconView;
597     }
598 
setupReturnDragAnimator(float fromX, float fromY, View originalView, TaskbarReturnPropertiesListener animListener)599     private void setupReturnDragAnimator(float fromX, float fromY, View originalView,
600             TaskbarReturnPropertiesListener animListener) {
601         // Finish any pending return animation before starting a new return
602         if (mReturnAnimator != null) {
603             mReturnAnimator.end();
604         }
605 
606         // For top level icons, the target is the icon itself
607         View target = findTaskbarTargetForIconView(originalView);
608 
609         int[] toPosition = target.getLocationOnScreen();
610         float iconSize = target.getWidth();
611         if (target instanceof BubbleTextView) {
612             Rect bounds = new Rect();
613             ((BubbleTextView) target).getSourceVisualDragBounds(bounds);
614             toPosition[0] += bounds.left;
615             toPosition[1] += bounds.top;
616             iconSize = bounds.width();
617         }
618         float toScale = iconSize / mDragIconSize;
619         float toAlpha = (target == originalView) ? 1f : 0f;
620         MultiValueUpdateListener listener = new MultiValueUpdateListener() {
621             final FloatProp mDx = new FloatProp(fromX, toPosition[0], 0,
622                     ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.FAST_OUT_SLOW_IN);
623             final FloatProp mDy = new FloatProp(fromY, toPosition[1], 0,
624                     ANIM_DURATION_RETURN_ICON_TO_TASKBAR,
625                     FAST_OUT_SLOW_IN);
626             final FloatProp mScale = new FloatProp(1f, toScale, 0,
627                     ANIM_DURATION_RETURN_ICON_TO_TASKBAR, FAST_OUT_SLOW_IN);
628             final FloatProp mAlpha = new FloatProp(1f, toAlpha, 0,
629                     ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCEL_2);
630             @Override
631             public void onUpdate(float percent, boolean initOnly) {
632                 animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value);
633             }
634         };
635         mReturnAnimator = ValueAnimator.ofFloat(0f, 1f);
636         mReturnAnimator.setDuration(ANIM_DURATION_RETURN_ICON_TO_TASKBAR);
637         mReturnAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
638         mReturnAnimator.addUpdateListener(listener);
639     }
640 
641     @Override
getX(MotionEvent ev)642     protected float getX(MotionEvent ev) {
643         // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
644         // we start at the correct position even though touch down is on the smaller DragLayer size.
645         return ev.getRawX();
646     }
647 
648     @Override
getY(MotionEvent ev)649     protected float getY(MotionEvent ev) {
650         // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
651         // we start at the correct position even though touch down is on the smaller DragLayer size.
652         return ev.getRawY();
653     }
654 
655     @Override
getClampedDragLayerPos(float x, float y)656     protected Point getClampedDragLayerPos(float x, float y) {
657         // No need to clamp, as we will take up the entire screen.
658         mTmpPoint.set(Math.round(x), Math.round(y));
659         return mTmpPoint;
660     }
661 
662     @Override
exitDrag()663     protected void exitDrag() {
664         if (mDragObject != null && !mDisallowGlobalDrag) {
665             mActivity.getDragLayer().removeView(mDragObject.dragView);
666         }
667     }
668 
669     @Override
addDropTarget(DropTarget target)670     public void addDropTarget(DropTarget target) {
671         // No-op as Taskbar currently doesn't support any drop targets internally.
672         // Note: if we do add internal DropTargets, we'll still need to ignore Folder.
673     }
674 
675     @Override
getDefaultDropTarget(int[] dropCoordinates)676     protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
677         return null;
678     }
679 
680     interface TaskbarReturnPropertiesListener {
updateDragShadow(float x, float y, float scale, float alpha)681         void updateDragShadow(float x, float y, float scale, float alpha);
682     }
683 
684     @Override
dumpLogs(String prefix, PrintWriter pw)685     public void dumpLogs(String prefix, PrintWriter pw) {
686         pw.println(prefix + "TaskbarDragController:");
687 
688         pw.println(prefix + "\tmDragIconSize=" + mDragIconSize);
689         pw.println(prefix + "\tmTempXY=" + Arrays.toString(mTempXY));
690         pw.println(prefix + "\tmRegistrationX=" + mRegistrationX);
691         pw.println(prefix + "\tmRegistrationY=" + mRegistrationY);
692         pw.println(prefix + "\tmIsSystemDragInProgress=" + mIsSystemDragInProgress);
693         pw.println(prefix + "\tisInternalDragInProgess=" + super.isDragging());
694         pw.println(prefix + "\tmDisallowGlobalDrag=" + mDisallowGlobalDrag);
695         pw.println(prefix + "\tmDisallowLongClick=" + mDisallowLongClick);
696     }
697 }
698