• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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