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