• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.documentsui.selection;
17 
18 import static com.android.documentsui.selection.Shared.DEBUG;
19 import static com.android.documentsui.selection.Shared.VERBOSE;
20 
21 import android.util.Log;
22 import android.view.MotionEvent;
23 
24 import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails;
25 import com.android.internal.widget.RecyclerView;
26 
27 import javax.annotation.Nullable;
28 
29 /**
30  * A MotionInputHandler that provides the high-level glue for mouse/stylus driven selection. This
31  * class works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper}
32  * to provide robust user drive selection support.
33  */
34 public final class MouseInputHandler extends MotionInputHandler {
35 
36         private static final String TAG = "MouseInputDelegate";
37 
38         private final Callbacks mCallbacks;
39 
40         // The event has been handled in onSingleTapUp
41         private boolean mHandledTapUp;
42         // true when the previous event has consumed a right click motion event
43         private boolean mHandledOnDown;
44 
MouseInputHandler( SelectionHelper selectionHelper, ItemDetailsLookup detailsLookup, Callbacks callbacks)45         public MouseInputHandler(
46                 SelectionHelper selectionHelper,
47                 ItemDetailsLookup detailsLookup,
48                 Callbacks callbacks) {
49 
50             super(selectionHelper, detailsLookup, callbacks);
51 
52             mCallbacks = callbacks;
53         }
54 
55         @Override
onDown(MotionEvent e)56         public boolean onDown(MotionEvent e) {
57             if (VERBOSE) Log.v(TAG, "Delegated onDown event.");
58             if ((MotionEvents.isAltKeyPressed(e) && MotionEvents.isPrimaryButtonPressed(e))
59                     || MotionEvents.isSecondaryButtonPressed(e)) {
60                 mHandledOnDown = true;
61                 return onRightClick(e);
62             }
63 
64             return false;
65         }
66 
67         @Override
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)68         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
69             // Don't scroll content window in response to mouse drag
70             // If it's two-finger trackpad scrolling, we want to scroll
71             return !MotionEvents.isTouchpadScroll(e2);
72         }
73 
74         @Override
onSingleTapUp(MotionEvent e)75         public boolean onSingleTapUp(MotionEvent e) {
76             // See b/27377794. Since we don't get a button state back from UP events, we have to
77             // explicitly save this state to know whether something was previously handled by
78             // DOWN events or not.
79             if (mHandledOnDown) {
80                 if (VERBOSE) Log.v(TAG, "Ignoring onSingleTapUp, previously handled in onDown.");
81                 mHandledOnDown = false;
82                 return false;
83             }
84 
85             if (!mDetailsLookup.overStableItem(e)) {
86                 if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
87                 mSelectionHelper.clearSelection();
88                 mCallbacks.clearFocus();
89                 return false;
90             }
91 
92             if (MotionEvents.isTertiaryButtonPressed(e)) {
93                 if (DEBUG) Log.d(TAG, "Ignoring middle click");
94                 return false;
95             }
96 
97             ItemDetails item = mDetailsLookup.getItemDetails(e);
98             if (mSelectionHelper.hasSelection()) {
99                 if (isRangeExtension(e)) {
100                     extendSelectionRange(item);
101                 } else {
102                     if (shouldClearSelection(e, item)) {
103                         mSelectionHelper.clearSelection();
104                     }
105                     if (mSelectionHelper.isSelected(item.getStableId())) {
106                         if (mSelectionHelper.deselect(item.getStableId())) {
107                             mCallbacks.clearFocus();
108                         }
109                     } else {
110                         selectOrFocusItem(item, e);
111                     }
112                 }
113                 mHandledTapUp = true;
114                 return true;
115             }
116 
117             return false;
118         }
119 
120         @Override
onSingleTapConfirmed(MotionEvent e)121         public boolean onSingleTapConfirmed(MotionEvent e) {
122             if (mHandledTapUp) {
123                 if (VERBOSE) Log.v(TAG,
124                         "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
125                 mHandledTapUp = false;
126                 return false;
127             }
128 
129             if (mSelectionHelper.hasSelection()) {
130                 return false;  // should have been handled by onSingleTapUp.
131             }
132 
133             if (!mDetailsLookup.overItem(e)) {
134                 if (DEBUG) Log.d(TAG, "Ignoring Confirmed Tap on non-item.");
135                 return false;
136             }
137 
138             if (MotionEvents.isTertiaryButtonPressed(e)) {
139                 if (DEBUG) Log.d(TAG, "Ignoring middle click");
140                 return false;
141             }
142 
143             @Nullable ItemDetails item = mDetailsLookup.getItemDetails(e);
144             if (item == null || !item.hasStableId()) {
145                 Log.w(TAG, "Ignoring Confirmed Tap. No document details associated w/ event.");
146                 return false;
147             }
148 
149             if (mCallbacks.hasFocusedItem() && MotionEvents.isShiftKeyPressed(e)) {
150                 mSelectionHelper.startRange(mCallbacks.getFocusedPosition());
151                 mSelectionHelper.extendRange(item.getPosition());
152             } else {
153                 selectOrFocusItem(item, e);
154             }
155             return true;
156         }
157 
158         @Override
onDoubleTap(MotionEvent e)159         public boolean onDoubleTap(MotionEvent e) {
160             mHandledTapUp = false;
161 
162             if (!mDetailsLookup.overStableItem(e)) {
163                 if (DEBUG) Log.d(TAG, "Ignoring DoubleTap on non-model-backed item.");
164                 return false;
165             }
166 
167             if (MotionEvents.isTertiaryButtonPressed(e)) {
168                 if (DEBUG) Log.d(TAG, "Ignoring middle click");
169                 return false;
170             }
171 
172             @Nullable ItemDetails item = mDetailsLookup.getItemDetails(e);
173             if (item != null) {
174                 return mCallbacks.onItemActivated(item, e);
175             }
176 
177             return false;
178         }
179 
onRightClick(MotionEvent e)180         private boolean onRightClick(MotionEvent e) {
181             if (mDetailsLookup.overStableItem(e)) {
182                 @Nullable ItemDetails item = mDetailsLookup.getItemDetails(e);
183                 if (item != null && !mSelectionHelper.isSelected(item.getStableId())) {
184                     mSelectionHelper.clearSelection();
185                     selectItem(item);
186                 }
187             }
188 
189             // We always delegate final handling of the event,
190             // since the handler might want to show a context menu
191             // in an empty area or some other weirdo view.
192             return mCallbacks.onContextClick(e);
193         }
194 
selectOrFocusItem(ItemDetails item, MotionEvent e)195         private void selectOrFocusItem(ItemDetails item, MotionEvent e) {
196             if (item.inSelectionHotspot(e) || MotionEvents.isCtrlKeyPressed(e)) {
197                 selectItem(item);
198             } else {
199                 focusItem(item);
200             }
201         }
202 
203         public static abstract class Callbacks extends MotionInputHandler.Callbacks {
onItemActivated(ItemDetails item, MotionEvent e)204             public abstract boolean onItemActivated(ItemDetails item, MotionEvent e);
onContextClick(MotionEvent e)205             public boolean onContextClick(MotionEvent e) {
206                 return false;
207             }
hasFocusedItem()208             public boolean hasFocusedItem() {
209                 return false;
210             }
getFocusedPosition()211             public int getFocusedPosition() {
212                 return RecyclerView.NO_POSITION;
213             }
214         }
215     }