1 /*
2  * Copyright (C) 2018 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.touch;
17 
18 import static android.view.View.INVISIBLE;
19 import static android.view.View.VISIBLE;
20 
21 import static com.android.launcher3.LauncherState.ALL_APPS;
22 import static com.android.launcher3.LauncherState.EDIT_MODE;
23 import static com.android.launcher3.LauncherState.NORMAL;
24 import static com.android.launcher3.LauncherState.OVERVIEW;
25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED;
26 
27 import android.graphics.Point;
28 import android.graphics.Rect;
29 import android.view.View;
30 import android.view.View.OnLongClickListener;
31 
32 import com.android.launcher3.DragSource;
33 import com.android.launcher3.DropTarget;
34 import com.android.launcher3.Launcher;
35 import com.android.launcher3.celllayout.CellInfo;
36 import com.android.launcher3.config.FeatureFlags;
37 import com.android.launcher3.dragndrop.DragController;
38 import com.android.launcher3.dragndrop.DragOptions;
39 import com.android.launcher3.folder.Folder;
40 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
41 import com.android.launcher3.model.data.ItemInfo;
42 import com.android.launcher3.model.data.PrivateSpaceInstallAppButtonInfo;
43 import com.android.launcher3.testing.TestLogging;
44 import com.android.launcher3.testing.shared.TestProtocol;
45 import com.android.launcher3.views.BubbleTextHolder;
46 import com.android.launcher3.widget.LauncherAppWidgetHostView;
47 import com.android.launcher3.widget.NavigableAppWidgetHostView;
48 import com.android.launcher3.widget.PendingItemDragHelper;
49 import com.android.launcher3.widget.WidgetCell;
50 import com.android.launcher3.widget.WidgetImageView;
51 
52 /**
53  * Class to handle long-clicks on workspace items and start drag as a result.
54  */
55 public class ItemLongClickListener {
56 
57     public static final OnLongClickListener INSTANCE_WORKSPACE =
58             ItemLongClickListener::onWorkspaceItemLongClick;
59 
60     public static final OnLongClickListener INSTANCE_ALL_APPS =
61             ItemLongClickListener::onAllAppsItemLongClick;
62 
onWorkspaceItemLongClick(View v)63     private static boolean onWorkspaceItemLongClick(View v) {
64         if (v instanceof LauncherAppWidgetHostView) {
65             TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
66         } else {
67             TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
68         }
69         Launcher launcher = Launcher.getLauncher(v.getContext());
70         if (!canStartDrag(launcher)) return false;
71         if (!launcher.isInState(NORMAL)
72                 && !launcher.isInState(OVERVIEW)
73                 && !launcher.isInState(EDIT_MODE)) {
74             return false;
75         }
76         if (!(v.getTag() instanceof ItemInfo)) return false;
77 
78         launcher.setWaitingForResult(null);
79         beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
80         return true;
81     }
82 
beginDrag(View v, Launcher launcher, ItemInfo info, DragOptions dragOptions)83     public static void beginDrag(View v, Launcher launcher, ItemInfo info,
84             DragOptions dragOptions) {
85         if (info.container >= 0) {
86             Folder folder = Folder.getOpen(launcher);
87             if (folder != null) {
88                 if (!folder.getIconsInReadingOrder().contains(v)) {
89                     folder.close(true);
90                 } else {
91                     folder.startDrag(v, dragOptions);
92                     return;
93                 }
94             }
95         }
96 
97         CellInfo longClickCellInfo = new CellInfo(v, info,
98                 launcher.getCellPosMapper().mapModelToPresenter(info));
99         launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
100     }
101 
onWidgetItemLongClick(WidgetCell v)102     private static boolean onWidgetItemLongClick(WidgetCell v) {
103         // Get the widget preview as the drag representation
104         WidgetImageView image = v.getWidgetView();
105         Launcher launcher = Launcher.getLauncher(v.getContext());
106         DragSource dragSource = (target, dragObject, success) -> { };
107 
108         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
109         // we abort the drag.
110         if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) {
111             return false;
112         }
113 
114         PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
115         // RemoteViews are being rendered in AppWidgetHostView in WidgetCell. And thus, the scale of
116         // RemoteViews is equivalent to the AppWidgetHostView scale.
117         dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview(), v.getAppWidgetHostViewScale());
118         dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
119 
120         if (image.getDrawable() != null) {
121             int[] loc = new int[2];
122             launcher.getDragLayer().getLocationInDragLayer(image, loc);
123 
124             dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
125                     image.getWidth(), new Point(loc[0], loc[1]), dragSource, new DragOptions());
126         } else {
127             NavigableAppWidgetHostView preview = v.getAppWidgetHostViewPreview();
128             int[] loc = new int[2];
129             launcher.getDragLayer().getLocationInDragLayer(preview, loc);
130             Rect r = new Rect();
131             preview.getWorkspaceVisualDragBounds(r);
132             dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(),
133                     new Point(loc[0], loc[1]), dragSource, new DragOptions());
134         }
135         return true;
136     }
137 
onAllAppsItemLongClick(View view)138     private static boolean onAllAppsItemLongClick(View view) {
139         if (view instanceof WidgetCell wc) {
140             return onWidgetItemLongClick(wc);
141         }
142         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onAllAppsItemLongClick");
143         view.cancelLongPress();
144         View v = (view instanceof BubbleTextHolder)
145                 ? ((BubbleTextHolder) view).getBubbleText()
146                 : view;
147         Launcher launcher = Launcher.getLauncher(v.getContext());
148         if (!canStartDrag(launcher)) return false;
149         // When we have exited all apps or are in transition, disregard long clicks
150         if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
151         if (launcher.getWorkspace().isSwitchingState()) return false;
152 
153         StatsLogger logger = launcher.getStatsLogManager().logger();
154         if (v.getTag() instanceof ItemInfo itemInfo) {
155             if (itemInfo instanceof PrivateSpaceInstallAppButtonInfo) {
156                 return false;
157             }
158             logger.withItemInfo((ItemInfo) v.getTag());
159         }
160         logger.log(LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED);
161 
162         // Start the drag
163         final DragController dragController = launcher.getDragController();
164         dragController.addDragListener(new DragController.DragListener() {
165             @Override
166             public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
167                 v.setVisibility(INVISIBLE);
168             }
169 
170             @Override
171             public void onDragEnd() {
172                 v.setVisibility(VISIBLE);
173                 dragController.removeDragListener(this);
174             }
175         });
176 
177         launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), new DragOptions());
178         return false;
179     }
180 
canStartDrag(Launcher launcher)181     public static boolean canStartDrag(Launcher launcher) {
182         if (launcher == null) {
183             return false;
184         }
185         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
186         // that is subsequently removed from the workspace in startBinding().
187         if (launcher.isWorkspaceLocked()) return false;
188         // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
189         if (launcher.getDragController().isDragging()) return false;
190         // Return early if user is in the middle of selecting split-screen apps
191         if (FeatureFlags.enableSplitContextually() && launcher.isSplitSelectionActive()) {
192             return false;
193         }
194 
195         return true;
196     }
197 }
198