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.dragndrop; 17 18 import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE; 19 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; 20 import static com.android.launcher3.LauncherState.EDIT_MODE; 21 import static com.android.launcher3.LauncherState.NORMAL; 22 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 23 24 import android.content.res.Resources; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.view.HapticFeedbackConstants; 28 import android.view.View; 29 30 import androidx.annotation.Nullable; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.launcher3.AbstractFloatingView; 34 import com.android.launcher3.DragSource; 35 import com.android.launcher3.DropTarget; 36 import com.android.launcher3.Launcher; 37 import com.android.launcher3.R; 38 import com.android.launcher3.accessibility.DragViewStateAnnouncer; 39 import com.android.launcher3.model.data.ItemInfo; 40 import com.android.launcher3.widget.util.WidgetDragScaleUtils; 41 42 /** 43 * Drag controller for Launcher activity 44 */ 45 public class LauncherDragController extends DragController<Launcher> { 46 47 private static final boolean PROFILE_DRAWING_DURING_DRAG = false; 48 private final FlingToDeleteHelper mFlingToDeleteHelper; 49 LauncherDragController(Launcher launcher)50 public LauncherDragController(Launcher launcher) { 51 super(launcher); 52 mFlingToDeleteHelper = new FlingToDeleteHelper(launcher); 53 } 54 55 @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)56 protected DragView startDrag( 57 @Nullable Drawable drawable, 58 @Nullable View view, 59 DraggableView originalView, 60 int dragLayerX, 61 int dragLayerY, 62 DragSource source, 63 ItemInfo dragInfo, 64 Rect dragRegion, 65 float initialDragViewScale, 66 float dragViewScaleOnDrop, 67 DragOptions options) { 68 if (PROFILE_DRAWING_DURING_DRAG) { 69 android.os.Debug.startMethodTracing("Launcher"); 70 } 71 72 mActivity.hideKeyboard(); 73 AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_DISCOVERY_BOUNCE); 74 75 mOptions = options; 76 if (mOptions.simulatedDndStartPoint != null) { 77 mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x; 78 mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y; 79 } 80 81 final int registrationX = mMotionDown.x - dragLayerX; 82 final int registrationY = mMotionDown.y - dragLayerY; 83 84 final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; 85 final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; 86 87 mLastDropTarget = null; 88 89 mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext()); 90 mDragObject.originalView = originalView; 91 92 mIsInPreDrag = mOptions.preDragCondition != null 93 && !mOptions.preDragCondition.shouldStartDrag(0); 94 95 final Resources res = mActivity.getResources(); 96 97 final float scalePx; 98 if (originalView.getViewType() == DraggableView.DRAGGABLE_WIDGET) { 99 scalePx = mIsInPreDrag ? 0f : getWidgetDragScalePx(drawable, view, dragInfo); 100 } else { 101 scalePx = mIsInPreDrag ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f; 102 } 103 final DragView dragView = mDragObject.dragView = drawable != null 104 ? new LauncherDragView( 105 mActivity, 106 drawable, 107 registrationX, 108 registrationY, 109 initialDragViewScale, 110 dragViewScaleOnDrop, 111 scalePx) 112 : new LauncherDragView( 113 mActivity, 114 view, 115 view.getMeasuredWidth(), 116 view.getMeasuredHeight(), 117 registrationX, 118 registrationY, 119 initialDragViewScale, 120 dragViewScaleOnDrop, 121 scalePx); 122 dragView.setItemInfo(dragInfo); 123 mDragObject.dragComplete = false; 124 125 mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft); 126 mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop); 127 128 mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent); 129 if (!mOptions.isAccessibleDrag) { 130 mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); 131 } 132 133 mDragObject.dragSource = source; 134 mDragObject.dragInfo = dragInfo; 135 mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy(); 136 137 if (mOptions.preDragCondition != null) { 138 dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0 || 139 mOptions.preDragCondition.getDragOffset().y != 0); 140 } 141 142 if (dragRegion != null) { 143 dragView.setDragRegion(new Rect(dragRegion)); 144 } 145 146 mActivity.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 147 dragView.show(mLastTouch.x, mLastTouch.y); 148 mDistanceSinceScroll = 0; 149 150 if (!mIsInPreDrag) { 151 callOnDragStart(); 152 } else if (mOptions.preDragCondition != null) { 153 mOptions.preDragCondition.onPreDragStart(mDragObject); 154 } 155 156 handleMoveEvent(mLastTouch.x, mLastTouch.y); 157 158 if (!isItemPinnable() 159 || (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null)) { 160 // If it is an internal drag and the touch is already complete, cancel immediately 161 MAIN_EXECUTOR.post(this::cancelDrag); 162 } 163 return dragView; 164 } 165 166 167 /** 168 * Returns the scale in terms of pixels (to be applied on width) to scale the preview 169 * during drag and drop. 170 */ 171 @VisibleForTesting getWidgetDragScalePx(@ullable Drawable drawable, @Nullable View view, ItemInfo dragInfo)172 float getWidgetDragScalePx(@Nullable Drawable drawable, @Nullable View view, 173 ItemInfo dragInfo) { 174 float draggedViewWidthPx = 0; 175 float draggedViewHeightPx = 0; 176 177 if (view != null) { 178 draggedViewWidthPx = view.getMeasuredWidth(); 179 draggedViewHeightPx = view.getMeasuredHeight(); 180 } else if (drawable != null) { 181 draggedViewWidthPx = drawable.getIntrinsicWidth(); 182 draggedViewHeightPx = drawable.getIntrinsicHeight(); 183 } 184 185 return WidgetDragScaleUtils.getWidgetDragScalePx(mActivity, mActivity.getDeviceProfile(), 186 draggedViewWidthPx, draggedViewHeightPx, dragInfo); 187 } 188 189 @Override exitDrag()190 protected void exitDrag() { 191 if (!mActivity.isInState(EDIT_MODE)) { 192 mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); 193 } 194 } 195 196 @Override endWithFlingAnimation()197 protected boolean endWithFlingAnimation() { 198 Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject, mOptions); 199 if (flingAnimation != null) { 200 drop(mFlingToDeleteHelper.getDropTarget(), flingAnimation); 201 return true; 202 } 203 return super.endWithFlingAnimation(); 204 } 205 206 @Override endDrag()207 protected void endDrag() { 208 super.endDrag(); 209 mFlingToDeleteHelper.releaseVelocityTracker(); 210 } 211 212 @Override getDefaultDropTarget(int[] dropCoordinates)213 protected DropTarget getDefaultDropTarget(int[] dropCoordinates) { 214 mActivity.getDragLayer().mapCoordInSelfToDescendant(mActivity.getWorkspace(), 215 dropCoordinates); 216 return mActivity.getWorkspace(); 217 } 218 } 219