/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.taskbar; import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS; import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_DRAGDROP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.util.Log; import android.util.Pair; import android.view.DragEvent; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; import android.window.SurfaceSyncGroup; import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; import com.android.internal.logging.InstanceId; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragDriver; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; import com.android.launcher3.statehandlers.DesktopVisibilityController; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.views.BubbleTextHolder; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.util.LogUtils; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.systemui.shared.recents.model.Task; import com.android.wm.shell.draganddrop.DragAndDropConstants; import java.io.PrintWriter; import java.util.Arrays; import java.util.Collections; import java.util.function.Predicate; /** * Handles long click on Taskbar items to start a system drag and drop operation. */ public class TaskbarDragController extends DragController implements TaskbarControllers.LoggableTaskbarController { private static final String TAG = "TaskbarDragController"; private static final boolean DEBUG_DRAG_SHADOW_SURFACE = false; private static final int ANIM_DURATION_RETURN_ICON_TO_TASKBAR = 300; private final int mDragIconSize; private final int[] mTempXY = new int[2]; // Initialized in init. TaskbarControllers mControllers; // Where the initial touch was relative to the dragged icon. private int mRegistrationX; private int mRegistrationY; private boolean mIsSystemDragInProgress; // Animation for the drag shadow back into position after an unsuccessful drag private ValueAnimator mReturnAnimator; private boolean mDisallowGlobalDrag; private boolean mDisallowLongClick; public TaskbarDragController(BaseTaskbarContext activity) { super(activity); Resources resources = mActivity.getResources(); mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size); } public void init(TaskbarControllers controllers) { mControllers = controllers; } public void setDisallowGlobalDrag(boolean disallowGlobalDrag) { mDisallowGlobalDrag = disallowGlobalDrag; } public void setDisallowLongClick(boolean disallowLongClick) { mDisallowLongClick = disallowLongClick; } /** * Attempts to start a system drag and drop operation for the given View, using its tag to * generate the ClipDescription and Intent. * @return Whether {@link View#startDragAndDrop} started successfully. */ public boolean startDragOnLongClick(View view) { return startDragOnLongClick(view, null, null); } protected boolean startDragOnLongClick( DeepShortcutView shortcutView, Point iconShift) { return startDragOnLongClick( shortcutView.getBubbleText(), new ShortcutDragPreviewProvider(shortcutView.getIconView(), iconShift), iconShift); } private boolean startDragOnLongClick( View view, @Nullable DragPreviewProvider dragPreviewProvider, @Nullable Point iconShift) { if (view instanceof BubbleTextHolder) { view = ((BubbleTextHolder) view).getBubbleText(); } if (!(view instanceof BubbleTextView) || mDisallowLongClick) { return false; } TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onTaskbarItemLongClick"); BubbleTextView btv = (BubbleTextView) view; mActivity.onDragStart(); btv.post(() -> { DragView dragView = startInternalDrag(btv, dragPreviewProvider); if (iconShift != null) { dragView.animateShift(-iconShift.x, -iconShift.y); } btv.setIconDisabled(true); mControllers.taskbarAutohideSuspendController.updateFlag( TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true); }); return true; } private DragView startInternalDrag( BubbleTextView btv, @Nullable DragPreviewProvider dragPreviewProvider) { float iconScale = btv.getIcon().getAnimatedScale(); // Clear the pressed state if necessary btv.clearFocus(); btv.setPressed(false); btv.clearPressedBackground(); final DragPreviewProvider previewProvider = dragPreviewProvider == null ? new DragPreviewProvider(btv) : dragPreviewProvider; final Drawable drawable = previewProvider.createDrawable(); final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY); int dragLayerX = mTempXY[0]; int dragLayerY = mTempXY[1]; Rect dragRect = new Rect(); btv.getSourceVisualDragBounds(dragRect); dragLayerY += dragRect.top; DragOptions dragOptions = new DragOptions(); // First, see if view is a search result that needs custom pre-drag conditions. dragOptions.preDragCondition = mControllers.taskbarAllAppsController.createPreDragConditionForSearch(btv); if (dragOptions.preDragCondition == null) { // See if view supports a popup container. PopupContainerWithArrow popupContainer = mControllers.taskbarPopupController.showForIcon(btv); if (popupContainer != null) { dragOptions.preDragCondition = popupContainer.createPreDragCondition(false); } } if (dragOptions.preDragCondition == null) { // Fallback pre-drag condition. dragOptions.preDragCondition = new DragOptions.PreDragCondition() { private DragView mDragView; @Override public boolean shouldStartDrag(double distanceDragged) { return mDragView != null && mDragView.isScaleAnimationFinished(); } @Override public void onPreDragStart(DropTarget.DragObject dragObject) { mDragView = dragObject.dragView; if (!shouldStartDrag(0)) { mDragView.setOnScaleAnimEndCallback( TaskbarDragController.this::onPreDragAnimationEnd); } } @Override public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { mDragView = null; } }; } Point dragOffset = dragOptions.preDragCondition.getDragOffset(); return startDrag( drawable, /* view = */ null, /* originalView = */ btv, dragLayerX + dragOffset.x, dragLayerY + dragOffset.y, (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */, (ItemInfo) btv.getTag(), dragRect, scale * iconScale, scale, dragOptions); } @Override protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view, DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source, ItemInfo dragInfo, Rect dragRegion, float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) { mActivity.hideKeyboard(); mOptions = options; mRegistrationX = mMotionDown.x - dragLayerX; mRegistrationY = mMotionDown.y - dragLayerY; final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; mLastDropTarget = null; mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext()); mDragObject.originalView = originalView; mDragObject.deferDragViewCleanupPostAnimation = false; mIsInPreDrag = mOptions.preDragCondition != null && !mOptions.preDragCondition.shouldStartDrag(0); float scalePx = mDragIconSize - dragRegion.width(); final DragView dragView = mDragObject.dragView = new TaskbarDragView( mActivity, drawable, mRegistrationX, mRegistrationY, initialDragViewScale, dragViewScaleOnDrop, scalePx); dragView.setItemInfo(dragInfo); mDragObject.dragComplete = false; mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft); mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop); mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {}); if (!mOptions.isAccessibleDrag) { mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); } mDragObject.dragSource = source; mDragObject.dragInfo = dragInfo; mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy(); if (mOptions.preDragCondition != null) { dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0 || mOptions.preDragCondition.getDragOffset().y != 0); } if (dragRegion != null) { dragView.setDragRegion(new Rect(dragRegion)); } dragView.show(mLastTouch.x, mLastTouch.y); mDistanceSinceScroll = 0; if (!mIsInPreDrag) { callOnDragStart(); } else if (mOptions.preDragCondition != null) { mOptions.preDragCondition.onPreDragStart(mDragObject); } handleMoveEvent(mLastTouch.x, mLastTouch.y); return dragView; } /** Invoked when an animation running as part of pre-drag finishes. */ public void onPreDragAnimationEnd() { // Drag might be cancelled during the DragView animation, so check mIsPreDrag again. if (mIsInPreDrag) { callOnDragStart(); } } @Override protected void callOnDragStart() { super.callOnDragStart(); // TODO(297921594) clean it up when taskbar to desktop drag is implemented. DesktopVisibilityController desktopController = LauncherActivityInterface.INSTANCE.getDesktopVisibilityController(); // Pre-drag has ended, start the global system drag. if (mDisallowGlobalDrag || (desktopController != null && desktopController.areDesktopTasksVisible())) { AbstractFloatingView.closeAllOpenViewsExcept(mActivity, TYPE_TASKBAR_ALL_APPS); return; } startSystemDrag((BubbleTextView) mDragObject.originalView); } private void startSystemDrag(BubbleTextView btv) { View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) { @Override public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { int iconSize = Math.max(mDragIconSize, btv.getWidth()); if (iconSize > 0) { shadowSize.set(iconSize, iconSize); } else { Log.d(TAG, "Invalid icon size, dragSize=" + mDragIconSize + " viewWidth=" + btv.getWidth()); } // The registration point was taken before the icon scaled to mDragIconSize, so // offset the registration to where the touch is on the new size. int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2; int offsetY = (mDragIconSize - mDragObject.dragView.getDragRegionHeight()) / 2; int touchX = mRegistrationX + offsetX; int touchY = mRegistrationY + offsetY; if (touchX >= 0 && touchY >= 0) { shadowTouchPoint.set(touchX, touchY); } else { Log.d(TAG, "Invalid touch point, " + "registrationXY=(" + mRegistrationX + ", " + mRegistrationY + ") " + "offsetXY=(" + offsetX + ", " + offsetY + ")"); } } @Override public void onDrawShadow(Canvas canvas) { canvas.save(); if (DEBUG_DRAG_SHADOW_SURFACE) { canvas.drawColor(0xffff0000); } float scale = mDragObject.dragView.getEndScale(); canvas.scale(scale, scale); mDragObject.dragView.draw(canvas); canvas.restore(); } }; Object tag = btv.getTag(); ClipDescription clipDescription = null; Intent intent = null; if (tag instanceof ItemInfo) { ItemInfo item = (ItemInfo) tag; LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class); clipDescription = new ClipDescription(item.title, new String[] { item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT ? ClipDescription.MIMETYPE_APPLICATION_SHORTCUT : ClipDescription.MIMETYPE_APPLICATION_ACTIVITY }); intent = new Intent(); if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { String deepShortcutId = ((WorkspaceItemInfo) item).getDeepShortcutId(); intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, launcherApps.getShortcutIntent( item.getIntent().getPackage(), deepShortcutId, null, item.user)); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage()); intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId); } else if (item.itemType == ITEM_TYPE_SEARCH_ACTION) { // TODO(b/289261756): Buggy behavior when split opposite to an existing search pane. intent.putExtra( ClipDescription.EXTRA_PENDING_INTENT, PendingIntent.getActivityAsUser( mActivity, /* requestCode= */ 0, item.getIntent(), PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, /* options= */ null, item.user)); } else { intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, launcherApps.getMainActivityLaunchIntent(item.getIntent().getComponent(), null, item.user)); } intent.putExtra(Intent.EXTRA_USER, item.user); } else if (tag instanceof Task) { Task task = (Task) tag; clipDescription = new ClipDescription(task.titleDescription, new String[] { ClipDescription.MIMETYPE_APPLICATION_TASK }); intent = new Intent(); intent.putExtra(Intent.EXTRA_TASK_ID, task.key.id); intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId)); } if (clipDescription != null && intent != null) { Pair instanceIds = LogUtils.getShellShareableInstanceId(); // Need to share the same InstanceId between launcher3 and WM Shell (internal). InstanceId internalInstanceId = instanceIds.first; com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second; intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId); if (DisplayController.isTransientTaskbar(mActivity)) { // Tell WM Shell to ignore drag events in the provided transient taskbar region. TaskbarDragLayer dragLayer = mControllers.taskbarActivityContext.getDragLayer(); int[] locationOnScreen = dragLayer.getLocationOnScreen(); RectF disallowExternalDropRegion = new RectF(dragLayer.getLastDrawnTransientRect()); disallowExternalDropRegion.offset(locationOnScreen[0], locationOnScreen[1]); intent.putExtra(DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION, disallowExternalDropRegion); } ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent)); if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION)) { onSystemDragStarted(btv); mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo) .withInstanceId(launcherInstanceId) .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED); } } // Wait to close until after system drag has started, if applicable. AbstractFloatingView.closeAllOpenViews(mActivity); } private void onSystemDragStarted(BubbleTextView btv) { mIsSystemDragInProgress = true; mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> { switch (dragEvent.getAction()) { case DragEvent.ACTION_DRAG_STARTED: // Return true to tell system we are interested in events, so we get DRAG_ENDED. return true; case DragEvent.ACTION_DRAG_ENDED: mIsSystemDragInProgress = false; if (dragEvent.getResult()) { maybeOnDragEnd(); } else { // This will take care of calling maybeOnDragEnd() after the animation animateGlobalDragViewToOriginalPosition(btv, dragEvent); } mActivity.getDragLayer().setOnDragListener(null); return true; } return false; }); } @Override public boolean isDragging() { return super.isDragging() || mIsSystemDragInProgress; } /** {@code true} if the system is currently handling the drag. */ public boolean isSystemDragInProgress() { return mIsSystemDragInProgress; } private void maybeOnDragEnd() { if (!isDragging()) { ((BubbleTextView) mDragObject.originalView).setIconDisabled(false); mControllers.taskbarAutohideSuspendController.updateFlag( TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false); mActivity.onDragEnd(); if (mReturnAnimator == null) { // Upon successful drag, immediately stash taskbar. // Note, this must be done last to ensure no AutohideSuspendFlags are active, as // that will prevent us from stashing until the timeout. mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo) .log(LAUNCHER_APP_LAUNCH_DRAGDROP); } } } @Override protected void endDrag() { if (mDisallowGlobalDrag) { // We need to explicitly set deferDragViewCleanupPostAnimation to true here so the // super call doesn't remove it from the drag layer before the animation completes. // This variable gets set in to false in super.dispatchDropComplete() because it // (rightfully so, perhaps) thinks this drag operation has failed, and does its own // internal cleanup. // Another way to approach this would be to make all of overview a drop target and // accept the drop as successful and then run the setupReturnDragAnimator to simulate // drop failure to the user mDragObject.deferDragViewCleanupPostAnimation = true; float fromX = mDragObject.x - mDragObject.xOffset; float fromY = mDragObject.y - mDragObject.yOffset; DragView dragView = mDragObject.dragView; setupReturnDragAnimator(fromX, fromY, (View) mDragObject.originalView, (x, y, scale, alpha) -> { dragView.setTranslationX(x); dragView.setTranslationY(y); dragView.setScaleX(scale); dragView.setScaleY(scale); dragView.setAlpha(alpha); }); mReturnAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { callOnDragEnd(); dragView.remove(); dragView.clearAnimation(); // Do this after callOnDragEnd(), because we use mReturnAnimator != null to // imply the drag was canceled rather than successful. mReturnAnimator = null; } }); mReturnAnimator.start(); } super.endDrag(); } @Override protected void callOnDragEnd() { super.callOnDragEnd(); maybeOnDragEnd(); } private void animateGlobalDragViewToOriginalPosition(BubbleTextView btv, DragEvent dragEvent) { SurfaceControl dragSurface = dragEvent.getDragSurface(); // For top level icons, the target is the icon itself View target = findTaskbarTargetForIconView(btv); float fromX = dragEvent.getX() - dragEvent.getOffsetX(); float fromY = dragEvent.getY() - dragEvent.getOffsetY(); final ViewRootImpl viewRoot = target.getViewRootImpl(); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); setupReturnDragAnimator(fromX, fromY, btv, (x, y, scale, alpha) -> { tx.setPosition(dragSurface, x, y); tx.setScale(dragSurface, scale, scale); tx.setAlpha(dragSurface, alpha); tx.apply(); }); mReturnAnimator.addListener(new AnimatorListenerAdapter() { private boolean mCanceled = false; @Override public void onAnimationCancel(Animator animation) { cleanUpSurface(); mCanceled = true; } @Override public void onAnimationEnd(Animator animation) { if (mCanceled) { return; } cleanUpSurface(); } private void cleanUpSurface() { tx.close(); maybeOnDragEnd(); // Synchronize removing the drag surface with the next draw after calling // maybeOnDragEnd() SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); transaction.remove(dragSurface); SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TaskBarController"); syncGroup.addSyncCompleteCallback(mActivity.getMainExecutor(), transaction::close); syncGroup.add(viewRoot, null /* runnable */); syncGroup.addTransaction(transaction); syncGroup.markSyncReady(); // Do this after maybeOnDragEnd(), because we use mReturnAnimator != null to imply // the drag was canceled rather than successful. mReturnAnimator = null; } }); mReturnAnimator.start(); } private View findTaskbarTargetForIconView(@NonNull View iconView) { Object tag = iconView.getTag(); TaskbarViewController taskbarViewController = mControllers.taskbarViewController; if (tag instanceof ItemInfo) { ItemInfo item = (ItemInfo) tag; if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION || isInSearchResultContainer(item)) { if (mDisallowGlobalDrag) { // We're dragging in taskbarAllApps, we don't have folders or shortcuts return iconView; } // Since all apps closes when the drag starts, target the all apps button instead. return taskbarViewController.getAllAppsButtonView(); } else if (item.container >= 0) { // Since folders close when the drag starts, target the folder icon instead. Predicate matcher = ItemInfoMatcher.forFolderMatch( ItemInfoMatcher.ofItemIds(IntSet.wrap(item.id))); return taskbarViewController.getFirstIconMatch(matcher); } else if (item.itemType == ITEM_TYPE_DEEP_SHORTCUT) { // Find first icon with same package/user as the deep shortcut. Predicate packageUserMatcher = ItemInfoMatcher.ofPackages( Collections.singleton(item.getTargetPackage()), item.user); return taskbarViewController.getFirstIconMatch(packageUserMatcher); } } return iconView; } private static boolean isInSearchResultContainer(ItemInfo item) { ContainerInfo containerInfo = item.getContainerInfo(); return containerInfo.getContainerCase() == EXTENDED_CONTAINERS && containerInfo.getExtendedContainers().getContainerCase() == DEVICE_SEARCH_RESULT_CONTAINER; } private void setupReturnDragAnimator(float fromX, float fromY, View originalView, TaskbarReturnPropertiesListener animListener) { // Finish any pending return animation before starting a new return if (mReturnAnimator != null) { mReturnAnimator.end(); } // For top level icons, the target is the icon itself View target = findTaskbarTargetForIconView(originalView); int[] toPosition = target.getLocationOnScreen(); float iconSize = target.getWidth(); if (target instanceof BubbleTextView) { Rect bounds = new Rect(); ((BubbleTextView) target).getSourceVisualDragBounds(bounds); toPosition[0] += bounds.left; toPosition[1] += bounds.top; iconSize = bounds.width(); } float toScale = iconSize / mDragIconSize; float toAlpha = (target == originalView) ? 1f : 0f; MultiValueUpdateListener listener = new MultiValueUpdateListener() { final FloatProp mDx = new FloatProp(fromX, toPosition[0], FAST_OUT_SLOW_IN); final FloatProp mDy = new FloatProp(fromY, toPosition[1], FAST_OUT_SLOW_IN); final FloatProp mScale = new FloatProp(1f, toScale, FAST_OUT_SLOW_IN); final FloatProp mAlpha = new FloatProp(1f, toAlpha, Interpolators.ACCELERATE_2); @Override public void onUpdate(float percent, boolean initOnly) { animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value); } }; mReturnAnimator = ValueAnimator.ofFloat(0f, 1f); mReturnAnimator.setDuration(ANIM_DURATION_RETURN_ICON_TO_TASKBAR); mReturnAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); mReturnAnimator.addUpdateListener(listener); } @Override protected float getX(MotionEvent ev) { // We will resize to fill the screen while dragging, so use screen coordinates. This ensures // we start at the correct position even though touch down is on the smaller DragLayer size. return ev.getRawX(); } @Override protected float getY(MotionEvent ev) { // We will resize to fill the screen while dragging, so use screen coordinates. This ensures // we start at the correct position even though touch down is on the smaller DragLayer size. return ev.getRawY(); } @Override protected Point getClampedDragLayerPos(float x, float y) { // No need to clamp, as we will take up the entire screen. mTmpPoint.set(Math.round(x), Math.round(y)); return mTmpPoint; } @Override protected void exitDrag() { if (mDragObject != null && !mDisallowGlobalDrag) { mActivity.getDragLayer().removeView(mDragObject.dragView); } } @Override public void addDropTarget(DropTarget target) { // No-op as Taskbar currently doesn't support any drop targets internally. // Note: if we do add internal DropTargets, we'll still need to ignore Folder. } @Override protected DropTarget getDefaultDropTarget(int[] dropCoordinates) { return null; } interface TaskbarReturnPropertiesListener { void updateDragShadow(float x, float y, float scale, float alpha); } @Override public void dumpLogs(String prefix, PrintWriter pw) { pw.println(prefix + "TaskbarDragController:"); pw.println(prefix + "\tmDragIconSize=" + mDragIconSize); pw.println(prefix + "\tmTempXY=" + Arrays.toString(mTempXY)); pw.println(prefix + "\tmRegistrationX=" + mRegistrationX); pw.println(prefix + "\tmRegistrationY=" + mRegistrationY); pw.println(prefix + "\tmIsSystemDragInProgress=" + mIsSystemDragInProgress); pw.println(prefix + "\tisInternalDragInProgess=" + super.isDragging()); pw.println(prefix + "\tmDisallowGlobalDrag=" + mDisallowGlobalDrag); pw.println(prefix + "\tmDisallowLongClick=" + mDisallowLongClick); } }