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