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.NORMAL;
21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
22 
23 import android.content.res.Resources;
24 import android.graphics.Point;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.util.Log;
28 import android.view.HapticFeedbackConstants;
29 import android.view.View;
30 
31 import androidx.annotation.Nullable;
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.testing.TestProtocol;
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 
49     private final FlingToDeleteHelper mFlingToDeleteHelper;
50 
LauncherDragController(Launcher launcher)51     public LauncherDragController(Launcher launcher) {
52         super(launcher);
53         mFlingToDeleteHelper = new FlingToDeleteHelper(launcher);
54     }
55 
56     @Override
startDrag( @ullable Drawable drawable, @Nullable View view, DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options)57     protected DragView startDrag(
58             @Nullable Drawable drawable,
59             @Nullable View view,
60             DraggableView originalView,
61             int dragLayerX,
62             int dragLayerY,
63             DragSource source,
64             ItemInfo dragInfo,
65             Point dragOffset,
66             Rect dragRegion,
67             float initialDragViewScale,
68             float dragViewScaleOnDrop,
69             DragOptions options) {
70         if (TestProtocol.sDebugTracing) {
71             Log.d(TestProtocol.NO_DROP_TARGET, "5");
72         }
73         if (PROFILE_DRAWING_DURING_DRAG) {
74             android.os.Debug.startMethodTracing("Launcher");
75         }
76 
77         mActivity.hideKeyboard();
78         AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_DISCOVERY_BOUNCE);
79 
80         mOptions = options;
81         if (mOptions.simulatedDndStartPoint != null) {
82             mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x;
83             mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y;
84         }
85 
86         final int registrationX = mMotionDown.x - dragLayerX;
87         final int registrationY = mMotionDown.y - dragLayerY;
88 
89         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
90         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
91 
92         mLastDropTarget = null;
93 
94         mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
95         mDragObject.originalView = originalView;
96 
97         mIsInPreDrag = mOptions.preDragCondition != null
98                 && !mOptions.preDragCondition.shouldStartDrag(0);
99 
100         final Resources res = mActivity.getResources();
101         final float scaleDps = mIsInPreDrag
102                 ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
103         final DragView dragView = mDragObject.dragView = drawable != null
104                 ? new LauncherDragView(
105                 mActivity,
106                 drawable,
107                 registrationX,
108                 registrationY,
109                 initialDragViewScale,
110                 dragViewScaleOnDrop,
111                 scaleDps)
112                 : new LauncherDragView(
113                         mActivity,
114                         view,
115                         view.getMeasuredWidth(),
116                         view.getMeasuredHeight(),
117                         registrationX,
118                         registrationY,
119                         initialDragViewScale,
120                         dragViewScaleOnDrop,
121                         scaleDps);
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 (dragOffset != null) {
138             dragView.setDragVisualizeOffset(new Point(dragOffset));
139         }
140         if (dragRegion != null) {
141             dragView.setDragRegion(new Rect(dragRegion));
142         }
143 
144         mActivity.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
145         dragView.show(mLastTouch.x, mLastTouch.y);
146         mDistanceSinceScroll = 0;
147 
148         if (!mIsInPreDrag) {
149             callOnDragStart();
150         } else if (mOptions.preDragCondition != null) {
151             mOptions.preDragCondition.onPreDragStart(mDragObject);
152         }
153 
154         handleMoveEvent(mLastTouch.x, mLastTouch.y);
155 
156         if (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null) {
157             // If it is an internal drag and the touch is already complete, cancel immediately
158             MAIN_EXECUTOR.submit(this::cancelDrag);
159         }
160         return dragView;
161     }
162 
163     @Override
exitDrag()164     protected void exitDrag() {
165         mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
166     }
167 
168     @Override
endWithFlingAnimation()169     protected boolean endWithFlingAnimation() {
170         Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject, mOptions);
171         if (flingAnimation != null) {
172             drop(mFlingToDeleteHelper.getDropTarget(), flingAnimation);
173             return true;
174         }
175         return super.endWithFlingAnimation();
176     }
177 
178     @Override
endDrag()179     protected void endDrag() {
180         super.endDrag();
181         mFlingToDeleteHelper.releaseVelocityTracker();
182     }
183 
184     @Override
getDefaultDropTarget(int[] dropCoordinates)185     protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
186         mActivity.getDragLayer().mapCoordInSelfToDescendant(mActivity.getWorkspace(),
187                 dropCoordinates);
188         return mActivity.getWorkspace();
189     }
190 }
191