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