blob: 76aa492d8b08cf8f3a83685a082887e512af85ec [file] [log] [blame]
Steve McKay74956af2016-06-30 21:03:06 -07001/*
2 * Copyright (C) 2016 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
17package com.android.documentsui.dirlist;
18
Steve McKayd9caa6a2016-09-15 16:36:45 -070019import static com.android.documentsui.base.Shared.DEBUG;
Steve McKay30535bc2016-11-04 14:16:58 -070020import static com.android.documentsui.base.Shared.VERBOSE;
Steve McKay2d1e4a32016-07-13 13:03:16 -070021
22import android.support.annotation.VisibleForTesting;
23import android.util.Log;
Steve McKay74956af2016-06-30 21:03:06 -070024import android.view.GestureDetector;
25import android.view.KeyEvent;
26import android.view.MotionEvent;
27
Steve McKay6d20d192016-09-21 17:57:10 -070028import com.android.documentsui.ActionHandler;
Steve McKaybff980a2016-09-13 17:39:50 -070029import com.android.documentsui.base.EventHandler;
Steve McKayd9caa6a2016-09-15 16:36:45 -070030import com.android.documentsui.base.Events;
31import com.android.documentsui.base.Events.InputEvent;
Steve McKay4f78ba62016-10-04 16:48:49 -070032import com.android.documentsui.selection.SelectionManager;
Steve McKay74956af2016-06-30 21:03:06 -070033
Steve McKay1597e942016-09-09 11:54:05 -070034import java.util.Collections;
Steve McKay74956af2016-06-30 21:03:06 -070035import java.util.function.Function;
36import java.util.function.Predicate;
37
Steve McKaybde20e12016-09-08 19:12:54 -070038import javax.annotation.Nullable;
39
Steve McKay74956af2016-06-30 21:03:06 -070040/**
41 * Grand unified-ish gesture/event listener for items in the directory list.
42 */
Steve McKay2d1e4a32016-07-13 13:03:16 -070043public final class UserInputHandler<T extends InputEvent>
44 extends GestureDetector.SimpleOnGestureListener
Steve McKay5fd0cbc2016-08-08 15:35:34 -070045 implements DocumentHolder.KeyboardEventListener {
Steve McKay74956af2016-06-30 21:03:06 -070046
Steve McKay2d1e4a32016-07-13 13:03:16 -070047 private static final String TAG = "UserInputHandler";
48
Steve McKaybd9f05a2016-10-10 10:18:36 -070049 private ActionHandler mActions;
Steve McKay74956af2016-06-30 21:03:06 -070050 private final FocusHandler mFocusHandler;
Steve McKay4f78ba62016-10-04 16:48:49 -070051 private final SelectionManager mSelectionMgr;
Steve McKay2d1e4a32016-07-13 13:03:16 -070052 private final Function<MotionEvent, T> mEventConverter;
Steve McKay74956af2016-06-30 21:03:06 -070053 private final Predicate<DocumentDetails> mSelectable;
Steve McKay6d20d192016-09-21 17:57:10 -070054
Ben Linaa66c432016-10-12 12:11:24 -070055 private final EventHandler<InputEvent> mContextMenuClickHandler;
Steve McKay6d20d192016-09-21 17:57:10 -070056
Garfield Tan84bd0f12016-09-12 14:18:32 -070057 private final EventHandler<InputEvent> mTouchDragListener;
58 private final EventHandler<InputEvent> mGestureSelectHandler;
Steve McKay990f76e2016-09-16 12:36:58 -070059
Steve McKay74956af2016-06-30 21:03:06 -070060 private final TouchInputDelegate mTouchDelegate;
61 private final MouseInputDelegate mMouseDelegate;
Steve McKay2d1e4a32016-07-13 13:03:16 -070062 private final KeyInputHandler mKeyListener;
Steve McKay74956af2016-06-30 21:03:06 -070063
64 public UserInputHandler(
Steve McKaybd9f05a2016-10-10 10:18:36 -070065 ActionHandler actions,
Steve McKay74956af2016-06-30 21:03:06 -070066 FocusHandler focusHandler,
Steve McKay4f78ba62016-10-04 16:48:49 -070067 SelectionManager selectionMgr,
Steve McKay2d1e4a32016-07-13 13:03:16 -070068 Function<MotionEvent, T> eventConverter,
Steve McKay74956af2016-06-30 21:03:06 -070069 Predicate<DocumentDetails> selectable,
Ben Linaa66c432016-10-12 12:11:24 -070070 EventHandler<InputEvent> contextMenuClickHandler,
Garfield Tan84bd0f12016-09-12 14:18:32 -070071 EventHandler<InputEvent> touchDragListener,
72 EventHandler<InputEvent> gestureSelectHandler) {
Steve McKay74956af2016-06-30 21:03:06 -070073
Steve McKaybd9f05a2016-10-10 10:18:36 -070074 mActions = actions;
Steve McKay74956af2016-06-30 21:03:06 -070075 mFocusHandler = focusHandler;
Steve McKay6d20d192016-09-21 17:57:10 -070076 mSelectionMgr = selectionMgr;
Steve McKay74956af2016-06-30 21:03:06 -070077 mEventConverter = eventConverter;
Steve McKay74956af2016-06-30 21:03:06 -070078 mSelectable = selectable;
Ben Linaa66c432016-10-12 12:11:24 -070079 mContextMenuClickHandler = contextMenuClickHandler;
Steve McKayf0fceb42016-09-07 17:35:55 -070080 mTouchDragListener = touchDragListener;
Ben Lin40f44882016-08-04 10:42:59 -070081 mGestureSelectHandler = gestureSelectHandler;
Steve McKay74956af2016-06-30 21:03:06 -070082
83 mTouchDelegate = new TouchInputDelegate();
84 mMouseDelegate = new MouseInputDelegate();
Steve McKay2d1e4a32016-07-13 13:03:16 -070085 mKeyListener = new KeyInputHandler();
Steve McKay74956af2016-06-30 21:03:06 -070086 }
87
88 @Override
Ben Lindbcccd62016-08-05 16:47:22 -070089 public boolean onDown(MotionEvent e) {
90 try (T event = mEventConverter.apply(e)) {
91 return onDown(event);
92 }
93 }
94
95 @VisibleForTesting
96 boolean onDown(T event) {
97 return event.isMouseEvent()
98 ? mMouseDelegate.onDown(event)
99 : mTouchDelegate.onDown(event);
100 }
101
102 @Override
Ben Lin35f99e02016-08-31 12:46:04 -0700103 public boolean onScroll(MotionEvent e1, MotionEvent e2,
104 float distanceX, float distanceY) {
105 try (T event = mEventConverter.apply(e2)) {
106 return onScroll(event);
107 }
108 }
109
110 @VisibleForTesting
111 boolean onScroll(T event) {
112 return event.isMouseEvent()
113 ? mMouseDelegate.onScroll(event)
114 : mTouchDelegate.onScroll(event);
115 }
116
117 @Override
Steve McKay74956af2016-06-30 21:03:06 -0700118 public boolean onSingleTapUp(MotionEvent e) {
Steve McKay2d1e4a32016-07-13 13:03:16 -0700119 try (T event = mEventConverter.apply(e)) {
120 return onSingleTapUp(event);
Steve McKay74956af2016-06-30 21:03:06 -0700121 }
122 }
123
Steve McKay2d1e4a32016-07-13 13:03:16 -0700124 @VisibleForTesting
125 boolean onSingleTapUp(T event) {
126 return event.isMouseEvent()
127 ? mMouseDelegate.onSingleTapUp(event)
128 : mTouchDelegate.onSingleTapUp(event);
129 }
130
Steve McKay74956af2016-06-30 21:03:06 -0700131 @Override
132 public boolean onSingleTapConfirmed(MotionEvent e) {
Steve McKay2d1e4a32016-07-13 13:03:16 -0700133 try (T event = mEventConverter.apply(e)) {
134 return onSingleTapConfirmed(event);
Steve McKay74956af2016-06-30 21:03:06 -0700135 }
136 }
137
Steve McKay2d1e4a32016-07-13 13:03:16 -0700138 @VisibleForTesting
139 boolean onSingleTapConfirmed(T event) {
140 return event.isMouseEvent()
141 ? mMouseDelegate.onSingleTapConfirmed(event)
142 : mTouchDelegate.onSingleTapConfirmed(event);
143 }
144
Steve McKay74956af2016-06-30 21:03:06 -0700145 @Override
146 public boolean onDoubleTap(MotionEvent e) {
Steve McKay2d1e4a32016-07-13 13:03:16 -0700147 try (T event = mEventConverter.apply(e)) {
148 return onDoubleTap(event);
Steve McKay74956af2016-06-30 21:03:06 -0700149 }
150 }
151
Steve McKay2d1e4a32016-07-13 13:03:16 -0700152 @VisibleForTesting
153 boolean onDoubleTap(T event) {
154 return event.isMouseEvent()
155 ? mMouseDelegate.onDoubleTap(event)
156 : mTouchDelegate.onDoubleTap(event);
157 }
158
Steve McKay74956af2016-06-30 21:03:06 -0700159 @Override
160 public void onLongPress(MotionEvent e) {
Steve McKay2d1e4a32016-07-13 13:03:16 -0700161 try (T event = mEventConverter.apply(e)) {
162 onLongPress(event);
Steve McKay74956af2016-06-30 21:03:06 -0700163 }
164 }
165
Steve McKay2d1e4a32016-07-13 13:03:16 -0700166 @VisibleForTesting
167 void onLongPress(T event) {
168 if (event.isMouseEvent()) {
169 mMouseDelegate.onLongPress(event);
Steve McKaybde20e12016-09-08 19:12:54 -0700170 } else {
171 mTouchDelegate.onLongPress(event);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700172 }
Steve McKay2d1e4a32016-07-13 13:03:16 -0700173 }
174
Ben Lindbcccd62016-08-05 16:47:22 -0700175 // Only events from RecyclerView are fed into UserInputHandler#onDown.
176 // ListeningGestureDetector#onTouch directly calls this method to support context menu in empty
177 // view
178 boolean onRightClick(MotionEvent e) {
Steve McKay2d1e4a32016-07-13 13:03:16 -0700179 try (T event = mEventConverter.apply(e)) {
Ben Lindbcccd62016-08-05 16:47:22 -0700180 return mMouseDelegate.onRightClick(event);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700181 }
182 }
183
184 @Override
185 public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
186 return mKeyListener.onKey(doc, keyCode, event);
187 }
188
Steve McKay2d1e4a32016-07-13 13:03:16 -0700189 private boolean selectDocument(DocumentDetails doc) {
190 assert(doc != null);
Steve McKaybde20e12016-09-08 19:12:54 -0700191 assert(doc.hasModelId());
Steve McKay74956af2016-06-30 21:03:06 -0700192 mSelectionMgr.toggleSelection(doc.getModelId());
193 mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition());
194 return true;
195 }
196
Steve McKaybde20e12016-09-08 19:12:54 -0700197 private void extendSelectionRange(DocumentDetails doc) {
198 mSelectionMgr.snapRangeSelection(doc.getAdapterPosition());
199 }
200
Steve McKay2d1e4a32016-07-13 13:03:16 -0700201 boolean isRangeExtension(T event) {
202 return event.isShiftKeyDown() && mSelectionMgr.isRangeSelectionActive();
203 }
204
Ben Lin64ac1172016-09-06 17:48:46 -0700205 private boolean shouldClearSelection(T event, DocumentDetails doc) {
Steve McKaybde20e12016-09-08 19:12:54 -0700206 return !event.isCtrlKeyDown()
207 && !doc.isInSelectionHotspot(event)
208 && !mSelectionMgr.getSelection().contains(doc.getModelId());
Ben Lin64ac1172016-09-06 17:48:46 -0700209 }
210
Steve McKay1597e942016-09-09 11:54:05 -0700211 private static final String TTAG = "TouchInputDelegate";
Steve McKay74956af2016-06-30 21:03:06 -0700212 private final class TouchInputDelegate {
213
Ben Lindbcccd62016-08-05 16:47:22 -0700214 boolean onDown(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700215 if (VERBOSE) Log.v(TTAG, "Delegated onDown event.");
Ben Lindbcccd62016-08-05 16:47:22 -0700216 return false;
217 }
218
Ben Lin35f99e02016-08-31 12:46:04 -0700219 // Don't consume so the RecyclerView will get the event and will get touch-based scrolling
220 boolean onScroll(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700221 if (VERBOSE) Log.v(TTAG, "Delegated onScroll event.");
Ben Lin35f99e02016-08-31 12:46:04 -0700222 return false;
223 }
224
Steve McKay2d1e4a32016-07-13 13:03:16 -0700225 boolean onSingleTapUp(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700226 if (VERBOSE) Log.v(TTAG, "Delegated onSingleTapUp event.");
Steve McKay1597e942016-09-09 11:54:05 -0700227 if (!event.isOverModelItem()) {
228 if (DEBUG) Log.d(TTAG, "Tap not associated w/ model item. Clearing selection.");
Steve McKay2d1e4a32016-07-13 13:03:16 -0700229 mSelectionMgr.clearSelection();
230 return false;
231 }
232
Steve McKay1597e942016-09-09 11:54:05 -0700233 DocumentDetails doc = event.getDocumentDetails();
Steve McKay2d1e4a32016-07-13 13:03:16 -0700234 if (mSelectionMgr.hasSelection()) {
235 if (isRangeExtension(event)) {
Steve McKaybde20e12016-09-08 19:12:54 -0700236 extendSelectionRange(doc);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700237 } else {
Steve McKaybde20e12016-09-08 19:12:54 -0700238 selectDocument(doc);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700239 }
Steve McKay74956af2016-06-30 21:03:06 -0700240 return true;
241 }
242
Steve McKaybde20e12016-09-08 19:12:54 -0700243 // Touch events select if they occur in the selection hotspot,
244 // otherwise they activate.
245 return doc.isInSelectionHotspot(event)
246 ? selectDocument(doc)
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900247 : mActions.openDocument(doc, ActionHandler.VIEW_TYPE_PREVIEW,
248 ActionHandler.VIEW_TYPE_REGULAR);
Steve McKay74956af2016-06-30 21:03:06 -0700249 }
250
Steve McKay2d1e4a32016-07-13 13:03:16 -0700251 boolean onSingleTapConfirmed(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700252 if (VERBOSE) Log.v(TTAG, "Delegated onSingleTapConfirmed event.");
Steve McKay74956af2016-06-30 21:03:06 -0700253 return false;
254 }
255
Steve McKay2d1e4a32016-07-13 13:03:16 -0700256 boolean onDoubleTap(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700257 if (VERBOSE) Log.v(TTAG, "Delegated onDoubleTap event.");
Steve McKay74956af2016-06-30 21:03:06 -0700258 return false;
259 }
260
Steve McKay2d1e4a32016-07-13 13:03:16 -0700261 final void onLongPress(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700262 if (VERBOSE) Log.v(TTAG, "Delegated onLongPress event.");
Steve McKay1597e942016-09-09 11:54:05 -0700263 if (!event.isOverModelItem()) {
264 if (DEBUG) Log.d(TTAG, "Ignoring LongPress on non-model-backed item.");
Steve McKaybde20e12016-09-08 19:12:54 -0700265 return;
266 }
267
Steve McKay1597e942016-09-09 11:54:05 -0700268 DocumentDetails doc = event.getDocumentDetails();
Steve McKay2d1e4a32016-07-13 13:03:16 -0700269 if (isRangeExtension(event)) {
Steve McKaybde20e12016-09-08 19:12:54 -0700270 extendSelectionRange(doc);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700271 } else {
Ben Lin40f44882016-08-04 10:42:59 -0700272 if (!mSelectionMgr.getSelection().contains(doc.getModelId())) {
Steve McKaybde20e12016-09-08 19:12:54 -0700273 selectDocument(doc);
Ben Lin2d6caf32016-09-19 17:48:54 -0700274 // If we cannot select it, we didn't apply anchoring - therefore should not
275 // start gesture selection
276 if (mSelectable.test(doc)) {
277 mGestureSelectHandler.accept(event);
278 }
Ben Lin40f44882016-08-04 10:42:59 -0700279 } else {
Ben Lin35f99e02016-08-31 12:46:04 -0700280 // We only initiate drag and drop on long press for touch to allow regular
281 // touch-based scrolling
Steve McKay990f76e2016-09-16 12:36:58 -0700282 mTouchDragListener.accept(event);
Ben Lin40f44882016-08-04 10:42:59 -0700283 }
Steve McKay2d1e4a32016-07-13 13:03:16 -0700284 }
Steve McKay74956af2016-06-30 21:03:06 -0700285 }
286 }
287
Steve McKay1597e942016-09-09 11:54:05 -0700288 private static final String MTAG = "MouseInputDelegate";
Steve McKay74956af2016-06-30 21:03:06 -0700289 private final class MouseInputDelegate {
Steve McKay74956af2016-06-30 21:03:06 -0700290 // The event has been handled in onSingleTapUp
Steve McKay2d1e4a32016-07-13 13:03:16 -0700291 private boolean mHandledTapUp;
Ben Lindbcccd62016-08-05 16:47:22 -0700292 // true when the previous event has consumed a right click motion event
293 private boolean mHandledOnDown;
294
295 boolean onDown(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700296 if (VERBOSE) Log.v(MTAG, "Delegated onDown event.");
Ben Linaa66c432016-10-12 12:11:24 -0700297 if (event.isSecondaryButtonPressed()
298 || (event.isAltKeyDown() && event.isPrimaryButtonPressed())) {
Ben Lindbcccd62016-08-05 16:47:22 -0700299 mHandledOnDown = true;
300 return onRightClick(event);
301 }
Ben Lin64ac1172016-09-06 17:48:46 -0700302
Ben Lindbcccd62016-08-05 16:47:22 -0700303 return false;
304 }
Steve McKay74956af2016-06-30 21:03:06 -0700305
Ben Lin35f99e02016-08-31 12:46:04 -0700306 // Don't scroll content window in response to mouse drag
307 boolean onScroll(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700308 if (VERBOSE) Log.v(MTAG, "Delegated onScroll event.");
Ben Linf88bc0b2017-02-09 12:11:15 -0800309 // If it's two-finger trackpad scrolling, we want to scroll
310 return !event.isTouchpadScroll();
Ben Lin35f99e02016-08-31 12:46:04 -0700311 }
312
Steve McKay2d1e4a32016-07-13 13:03:16 -0700313 boolean onSingleTapUp(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700314 if (VERBOSE) Log.v(MTAG, "Delegated onSingleTapUp event.");
Ben Lindbcccd62016-08-05 16:47:22 -0700315
316 // See b/27377794. Since we don't get a button state back from UP events, we have to
317 // explicitly save this state to know whether something was previously handled by
318 // DOWN events or not.
319 if (mHandledOnDown) {
Steve McKay30535bc2016-11-04 14:16:58 -0700320 if (VERBOSE) Log.v(MTAG, "Ignoring onSingleTapUp, previously handled in onDown.");
Ben Lindbcccd62016-08-05 16:47:22 -0700321 mHandledOnDown = false;
322 return false;
Steve McKay74956af2016-06-30 21:03:06 -0700323 }
324
Steve McKay1597e942016-09-09 11:54:05 -0700325 if (!event.isOverModelItem()) {
326 if (DEBUG) Log.d(MTAG, "Tap not associated w/ model item. Clearing selection.");
Steve McKay2d1e4a32016-07-13 13:03:16 -0700327 mSelectionMgr.clearSelection();
328 return false;
329 }
330
Ben Linaa66c432016-10-12 12:11:24 -0700331 if (event.isTertiaryButtonPressed()) {
332 if (DEBUG) Log.d(MTAG, "Ignoring middle click");
333 return false;
334 }
335
Steve McKay1597e942016-09-09 11:54:05 -0700336 DocumentDetails doc = event.getDocumentDetails();
Steve McKay2d1e4a32016-07-13 13:03:16 -0700337 if (mSelectionMgr.hasSelection()) {
338 if (isRangeExtension(event)) {
Steve McKaybde20e12016-09-08 19:12:54 -0700339 extendSelectionRange(doc);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700340 } else {
Ben Lin64ac1172016-09-06 17:48:46 -0700341 if (shouldClearSelection(event, doc)) {
342 mSelectionMgr.clearSelection();
343 }
344 selectDocument(doc);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700345 }
346 mHandledTapUp = true;
Steve McKay74956af2016-06-30 21:03:06 -0700347 return true;
348 }
349
Steve McKaybde20e12016-09-08 19:12:54 -0700350 return false;
Steve McKay74956af2016-06-30 21:03:06 -0700351 }
352
Steve McKay2d1e4a32016-07-13 13:03:16 -0700353 boolean onSingleTapConfirmed(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700354 if (VERBOSE) Log.v(MTAG, "Delegated onSingleTapConfirmed event.");
Steve McKay2d1e4a32016-07-13 13:03:16 -0700355 if (mHandledTapUp) {
Steve McKay30535bc2016-11-04 14:16:58 -0700356 if (VERBOSE) Log.v(MTAG, "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
Steve McKay2d1e4a32016-07-13 13:03:16 -0700357 mHandledTapUp = false;
Steve McKay74956af2016-06-30 21:03:06 -0700358 return false;
359 }
360
361 if (mSelectionMgr.hasSelection()) {
362 return false; // should have been handled by onSingleTapUp.
363 }
364
Steve McKaybde20e12016-09-08 19:12:54 -0700365 if (!event.isOverItem()) {
Steve McKay1597e942016-09-09 11:54:05 -0700366 if (DEBUG) Log.d(MTAG, "Ignoring Confirmed Tap on non-item.");
Steve McKaybde20e12016-09-08 19:12:54 -0700367 return false;
368 }
369
Ben Linaa66c432016-10-12 12:11:24 -0700370 if (event.isTertiaryButtonPressed()) {
371 if (DEBUG) Log.d(MTAG, "Ignoring middle click");
372 return false;
373 }
374
Steve McKaybde20e12016-09-08 19:12:54 -0700375 @Nullable DocumentDetails doc = event.getDocumentDetails();
376 if (doc == null || !doc.hasModelId()) {
Steve McKay1597e942016-09-09 11:54:05 -0700377 Log.w(MTAG, "Ignoring Confirmed Tap. No document details associated w/ event.");
Steve McKay74956af2016-06-30 21:03:06 -0700378 return false;
379 }
380
Ben Lin75b7b902016-11-02 15:59:29 -0700381 if (mFocusHandler.hasFocusedItem() && event.isShiftKeyDown()) {
382 mSelectionMgr.formNewSelectionRange(mFocusHandler.getFocusPosition(),
383 doc.getAdapterPosition());
384 return true;
385 } else {
386 return selectDocument(doc);
387 }
Steve McKay74956af2016-06-30 21:03:06 -0700388 }
389
Steve McKay2d1e4a32016-07-13 13:03:16 -0700390 boolean onDoubleTap(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700391 if (VERBOSE) Log.v(MTAG, "Delegated onDoubleTap event.");
Steve McKay2d1e4a32016-07-13 13:03:16 -0700392 mHandledTapUp = false;
Steve McKaybde20e12016-09-08 19:12:54 -0700393
Steve McKay1597e942016-09-09 11:54:05 -0700394 if (!event.isOverModelItem()) {
395 if (DEBUG) Log.d(MTAG, "Ignoring DoubleTap on non-model-backed item.");
Steve McKaybde20e12016-09-08 19:12:54 -0700396 return false;
Steve McKay74956af2016-06-30 21:03:06 -0700397 }
Steve McKaybde20e12016-09-08 19:12:54 -0700398
Ben Linaa66c432016-10-12 12:11:24 -0700399 if (event.isTertiaryButtonPressed()) {
400 if (DEBUG) Log.d(MTAG, "Ignoring middle click");
401 return false;
402 }
403
Steve McKay1597e942016-09-09 11:54:05 -0700404 DocumentDetails doc = event.getDocumentDetails();
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900405 return mActions.openDocument(doc, ActionHandler.VIEW_TYPE_REGULAR,
406 ActionHandler.VIEW_TYPE_PREVIEW);
Steve McKay74956af2016-06-30 21:03:06 -0700407 }
408
Steve McKay2d1e4a32016-07-13 13:03:16 -0700409 final void onLongPress(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700410 if (VERBOSE) Log.v(MTAG, "Delegated onLongPress event.");
Steve McKaybde20e12016-09-08 19:12:54 -0700411 return;
Steve McKay74956af2016-06-30 21:03:06 -0700412 }
413
Ben Lindbcccd62016-08-05 16:47:22 -0700414 private boolean onRightClick(T event) {
Steve McKay30535bc2016-11-04 14:16:58 -0700415 if (VERBOSE) Log.v(MTAG, "Delegated onRightClick event.");
Steve McKay1597e942016-09-09 11:54:05 -0700416 if (event.isOverModelItem()) {
417 DocumentDetails doc = event.getDocumentDetails();
418 if (!mSelectionMgr.getSelection().contains(doc.getModelId())) {
419 mSelectionMgr.replaceSelection(Collections.singleton(doc.getModelId()));
420 mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition());
421 }
422 }
423
424 // We always delegate final handling of the event,
425 // since the handler might want to show a context menu
426 // in an empty area or some other weirdo view.
Ben Linaa66c432016-10-12 12:11:24 -0700427 return mContextMenuClickHandler.accept(event);
Steve McKay74956af2016-06-30 21:03:06 -0700428 }
Steve McKay74956af2016-06-30 21:03:06 -0700429 }
430
Steve McKay2d1e4a32016-07-13 13:03:16 -0700431 private final class KeyInputHandler {
432 // TODO: Refactor FocusManager to depend only on DocumentDetails so we can eliminate
433 // difficult to test dependency on DocumentHolder.
Steve McKay74956af2016-06-30 21:03:06 -0700434
Ben Linf085da02016-12-20 10:45:05 -0800435 boolean onKey(@Nullable DocumentHolder doc, int keyCode, KeyEvent event) {
Steve McKay2d1e4a32016-07-13 13:03:16 -0700436 // Only handle key-down events. This is simpler, consistent with most other UIs, and
437 // enables the handling of repeated key events from holding down a key.
438 if (event.getAction() != KeyEvent.ACTION_DOWN) {
439 return false;
Steve McKay74956af2016-06-30 21:03:06 -0700440 }
Steve McKay74956af2016-06-30 21:03:06 -0700441
Steve McKay2d1e4a32016-07-13 13:03:16 -0700442 // Ignore tab key events. Those should be handled by the top-level key handler.
443 if (keyCode == KeyEvent.KEYCODE_TAB) {
444 return false;
445 }
446
Ben Linb62d4e52016-12-19 12:01:11 -0800447 // Ignore events sent to Addon Holders.
Ben Linf085da02016-12-20 10:45:05 -0800448 if (doc != null) {
449 int itemType = doc.getItemViewType();
450 if (itemType == DocumentsAdapter.ITEM_TYPE_HEADER_MESSAGE
451 || itemType == DocumentsAdapter.ITEM_TYPE_INFLATED_MESSAGE
452 || itemType == DocumentsAdapter.ITEM_TYPE_SECTION_BREAK) {
453 return false;
454 }
Ben Linb62d4e52016-12-19 12:01:11 -0800455 }
456
Steve McKay2d1e4a32016-07-13 13:03:16 -0700457 if (mFocusHandler.handleKey(doc, keyCode, event)) {
458 // Handle range selection adjustments. Extending the selection will adjust the
459 // bounds of the in-progress range selection. Each time an unshifted navigation
460 // event is received, the range selection is restarted.
461 if (shouldExtendSelection(doc, event)) {
462 if (!mSelectionMgr.isRangeSelectionActive()) {
463 // Start a range selection if one isn't active
464 mSelectionMgr.startRangeSelection(doc.getAdapterPosition());
465 }
466 mSelectionMgr.snapRangeSelection(mFocusHandler.getFocusPosition());
467 } else {
468 mSelectionMgr.endRangeSelection();
Ben Lin41d7e222016-09-27 12:26:29 -0700469 mSelectionMgr.clearSelection();
Steve McKay74956af2016-06-30 21:03:06 -0700470 }
Steve McKay74956af2016-06-30 21:03:06 -0700471 return true;
Steve McKay2d1e4a32016-07-13 13:03:16 -0700472 }
Steve McKay74956af2016-06-30 21:03:06 -0700473
Steve McKay2d1e4a32016-07-13 13:03:16 -0700474 // Handle enter key events
475 switch (keyCode) {
476 case KeyEvent.KEYCODE_ENTER:
477 if (event.isShiftPressed()) {
478 selectDocument(doc);
479 }
480 // For non-shifted enter keypresses, fall through.
481 case KeyEvent.KEYCODE_DPAD_CENTER:
482 case KeyEvent.KEYCODE_BUTTON_A:
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900483 return mActions.openDocument(doc, ActionHandler.VIEW_TYPE_REGULAR,
484 ActionHandler.VIEW_TYPE_PREVIEW);
Steve McKay17b761e2016-09-20 17:26:46 -0700485 case KeyEvent.KEYCODE_SPACE:
Tomasz Mikolajewskid22cc182017-03-15 16:13:46 +0900486 return mActions.openDocument(doc, ActionHandler.VIEW_TYPE_PREVIEW,
487 ActionHandler.VIEW_TYPE_NONE);
Steve McKay2d1e4a32016-07-13 13:03:16 -0700488 }
Steve McKay74956af2016-06-30 21:03:06 -0700489
Steve McKay74956af2016-06-30 21:03:06 -0700490 return false;
491 }
492
Steve McKay2d1e4a32016-07-13 13:03:16 -0700493 private boolean shouldExtendSelection(DocumentDetails doc, KeyEvent event) {
494 if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) {
495 return false;
496 }
Steve McKay74956af2016-06-30 21:03:06 -0700497
Steve McKay2d1e4a32016-07-13 13:03:16 -0700498 return mSelectable.test(doc);
499 }
Steve McKay74956af2016-06-30 21:03:06 -0700500 }
Steve McKay74956af2016-06-30 21:03:06 -0700501}