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