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