Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.documentsui.dirlist; |
| 18 | |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 19 | import static com.android.documentsui.base.Shared.VERBOSE; |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 20 | |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 21 | import android.annotation.Nullable; |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 22 | import android.content.Context; |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 23 | import android.os.Build; |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 24 | import android.support.v7.widget.RecyclerView; |
| 25 | import android.support.v7.widget.RecyclerView.OnItemTouchListener; |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 26 | import android.util.Log; |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 27 | import android.view.GestureDetector; |
| 28 | import android.view.MotionEvent; |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 29 | import android.view.ScaleGestureDetector; |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 30 | |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 31 | import com.android.documentsui.base.BooleanConsumer; |
Steve McKay | bff980a | 2016-09-13 17:39:50 -0700 | [diff] [blame] | 32 | import com.android.documentsui.base.EventHandler; |
Steve McKay | d9caa6a | 2016-09-15 16:36:45 -0700 | [diff] [blame] | 33 | import com.android.documentsui.base.Events; |
| 34 | import com.android.documentsui.base.Events.InputEvent; |
| 35 | import com.android.documentsui.base.Events.MotionInputEvent; |
Steve McKay | aad7883 | 2017-03-09 10:11:12 -0800 | [diff] [blame] | 36 | import com.android.documentsui.base.Features; |
Steve McKay | 4f78ba6 | 2016-10-04 16:48:49 -0700 | [diff] [blame] | 37 | import com.android.documentsui.selection.BandController; |
| 38 | import com.android.documentsui.selection.GestureSelector; |
Ben Lin | 40f4488 | 2016-08-04 10:42:59 -0700 | [diff] [blame] | 39 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 40 | import java.util.function.Consumer; |
| 41 | |
Ben Lin | 40f4488 | 2016-08-04 10:42:59 -0700 | [diff] [blame] | 42 | //Receives event meant for both directory and empty view, and either pass them to |
| 43 | //{@link UserInputHandler} for simple gestures (Single Tap, Long-Press), or intercept them for |
| 44 | //other types of gestures (drag n' drop) |
Ben Lin | 7b38f34 | 2016-12-14 11:52:35 -0800 | [diff] [blame] | 45 | final class ListeningGestureDetector extends GestureDetector implements OnItemTouchListener { |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 46 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 47 | private static final String TAG = "ListeningGestureDetector"; |
| 48 | |
Steve McKay | aad7883 | 2017-03-09 10:11:12 -0800 | [diff] [blame] | 49 | private final Features mFeatures; |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 50 | private final GestureSelector mGestureSelector; |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 51 | private final EventHandler<InputEvent> mMouseDragListener; |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 52 | private final BooleanConsumer mRefreshLayoutEnabler; |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 53 | private final BandController mBandController; |
| 54 | private final MouseDelegate mMouseDelegate = new MouseDelegate(); |
| 55 | private final TouchDelegate mTouchDelegate = new TouchDelegate(); |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 56 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 57 | // Currently only initialized on IS_DEBUGGABLE builds. |
| 58 | private final @Nullable ScaleGestureDetector mScaleDetector; |
| 59 | |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 60 | public ListeningGestureDetector( |
Steve McKay | aad7883 | 2017-03-09 10:11:12 -0800 | [diff] [blame] | 61 | Features features, |
Ben Lin | 40f4488 | 2016-08-04 10:42:59 -0700 | [diff] [blame] | 62 | Context context, |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 63 | RecyclerView recView, |
Garfield Tan | 84bd0f1 | 2016-09-12 14:18:32 -0700 | [diff] [blame] | 64 | EventHandler<InputEvent> mouseDragListener, |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 65 | BooleanConsumer refreshLayoutEnabler, |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 66 | GestureSelector gestureSelector, |
Ben Lin | 40f4488 | 2016-08-04 10:42:59 -0700 | [diff] [blame] | 67 | UserInputHandler<? extends InputEvent> handler, |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 68 | @Nullable BandController bandController, |
| 69 | Consumer<Float> scaleHandler) { |
| 70 | |
Steve McKay | 74956af | 2016-06-30 21:03:06 -0700 | [diff] [blame] | 71 | super(context, handler); |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 72 | |
Steve McKay | aad7883 | 2017-03-09 10:11:12 -0800 | [diff] [blame] | 73 | mFeatures = features; |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 74 | mMouseDragListener = mouseDragListener; |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 75 | mRefreshLayoutEnabler = refreshLayoutEnabler; |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 76 | mGestureSelector = gestureSelector; |
| 77 | mBandController = bandController; |
| 78 | recView.addOnItemTouchListener(this); |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 79 | |
| 80 | mScaleDetector = !Build.IS_DEBUGGABLE |
| 81 | ? null |
| 82 | : new ScaleGestureDetector( |
| 83 | context, |
| 84 | new ScaleGestureDetector.SimpleOnScaleGestureListener() { |
| 85 | @Override |
| 86 | public boolean onScale(ScaleGestureDetector detector) { |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 87 | if (VERBOSE) Log.v(TAG, |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 88 | "Received scale event: " + detector.getScaleFactor()); |
| 89 | scaleHandler.accept(detector.getScaleFactor()); |
| 90 | return true; |
| 91 | } |
| 92 | }); |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | @Override |
| 96 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 97 | boolean handled = false; |
| 98 | |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 99 | // TODO: Re-wire event handling so that we're not dispatching |
| 100 | // events to to scaledetector's #onTouchEvent from this |
| 101 | // #onInterceptTouchEvent touch event. |
Steve McKay | aad7883 | 2017-03-09 10:11:12 -0800 | [diff] [blame] | 102 | if (mFeatures.isGestureScaleEnabled() |
Steve McKay | b03a59c | 2016-10-19 15:59:53 -0700 | [diff] [blame] | 103 | && mScaleDetector != null) { |
| 104 | mScaleDetector.onTouchEvent(e); |
| 105 | } |
| 106 | |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 107 | try (InputEvent event = MotionInputEvent.obtain(e, rv)) { |
| 108 | if (event.isMouseEvent()) { |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 109 | if (event.isActionDown()) { |
| 110 | mRefreshLayoutEnabler.accept(false); |
| 111 | } |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 112 | handled |= mMouseDelegate.onInterceptTouchEvent(event); |
| 113 | } else { |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 114 | // If user has started some gesture while RecyclerView is not at the top, disable |
| 115 | // refresh |
| 116 | if (event.isActionDown() && rv.computeVerticalScrollOffset() != 0) { |
| 117 | mRefreshLayoutEnabler.accept(false); |
| 118 | } |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 119 | handled |= mTouchDelegate.onInterceptTouchEvent(event); |
| 120 | } |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 121 | |
| 122 | |
| 123 | if (event.isActionUp()) { |
| 124 | mRefreshLayoutEnabler.accept(true); |
| 125 | } |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 126 | } |
Ben Lin | 40f4488 | 2016-08-04 10:42:59 -0700 | [diff] [blame] | 127 | |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 128 | // Forward all events to UserInputHandler. |
| 129 | // This is necessary since UserInputHandler needs to always see the first DOWN event. Or |
| 130 | // else all future UP events will be tossed. |
| 131 | handled |= onTouchEvent(e); |
Ben Lin | 40f4488 | 2016-08-04 10:42:59 -0700 | [diff] [blame] | 132 | |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 133 | return handled; |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | @Override |
| 137 | public void onTouchEvent(RecyclerView rv, MotionEvent e) { |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 138 | try (InputEvent event = MotionInputEvent.obtain(e, rv)) { |
| 139 | if (Events.isMouseEvent(e)) { |
| 140 | mMouseDelegate.onTouchEvent(event); |
| 141 | } else { |
| 142 | mTouchDelegate.onTouchEvent(rv, event); |
| 143 | } |
Ben Lin | d79edd1 | 2017-03-14 17:39:36 -0700 | [diff] [blame] | 144 | |
| 145 | if (event.isActionUp()) { |
| 146 | mRefreshLayoutEnabler.accept(true); |
| 147 | } |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 148 | } |
Ben Lin | 40f4488 | 2016-08-04 10:42:59 -0700 | [diff] [blame] | 149 | |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 150 | // Note: even though this event is being handled as part of gestures such as drag and band, |
| 151 | // continue forwarding to the GestureDetector. The detector needs to see the entire cluster |
| 152 | // of events in order to properly interpret other gestures, such as long press. |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 153 | onTouchEvent(e); |
| 154 | } |
| 155 | |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 156 | private class MouseDelegate { |
| 157 | boolean onInterceptTouchEvent(InputEvent e) { |
Steve McKay | f0fceb4 | 2016-09-07 17:35:55 -0700 | [diff] [blame] | 158 | if (Events.isMouseDragEvent(e)) { |
Steve McKay | 990f76e | 2016-09-16 12:36:58 -0700 | [diff] [blame] | 159 | return mMouseDragListener.accept(e); |
Ben Lin | 35f99e0 | 2016-08-31 12:46:04 -0700 | [diff] [blame] | 160 | } else if (mBandController != null && |
| 161 | (mBandController.shouldStart(e) || mBandController.shouldStop(e))) { |
| 162 | return mBandController.onInterceptTouchEvent(e); |
| 163 | } |
| 164 | return false; |
| 165 | } |
| 166 | |
| 167 | void onTouchEvent(InputEvent e) { |
| 168 | if (mBandController != null) { |
| 169 | mBandController.onTouchEvent(e); |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | private class TouchDelegate { |
| 175 | boolean onInterceptTouchEvent(InputEvent e) { |
| 176 | // Gesture Selector needs to be constantly fed events, so that when a long press does |
| 177 | // happen, we would have the last DOWN event that occurred to keep track of our anchor |
| 178 | // point |
| 179 | return mGestureSelector.onInterceptTouchEvent(e); |
| 180 | } |
| 181 | |
| 182 | // TODO: Make this take just an InputEvent, no RecyclerView |
| 183 | void onTouchEvent(RecyclerView rv, InputEvent e) { |
| 184 | mGestureSelector.onTouchEvent(rv, e); |
| 185 | } |
| 186 | } |
| 187 | |
Steve McKay | c47a918 | 2016-06-23 11:08:19 -0700 | [diff] [blame] | 188 | @Override |
| 189 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {} |
Steve McKay | 30535bc | 2016-11-04 14:16:58 -0700 | [diff] [blame] | 190 | } |