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