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