blob: cc81d3f5786ecfe0bce0e12fb8b2f1ea4b50a332 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui.dirlist;
import static com.android.documentsui.base.Shared.VERBOSE;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.Events.InputEvent;
import com.android.documentsui.base.Events.MotionInputEvent;
import com.android.documentsui.base.Features;
import com.android.documentsui.selection.BandController;
import com.android.documentsui.selection.GestureSelector;
import java.util.function.Consumer;
//Receives event meant for both directory and empty view, and either pass them to
//{@link UserInputHandler} for simple gestures (Single Tap, Long-Press), or intercept them for
//other types of gestures (drag n' drop)
final class ListeningGestureDetector extends GestureDetector implements OnItemTouchListener {
private static final String TAG = "ListeningGestureDetector";
private final Features mFeatures;
private final GestureSelector mGestureSelector;
private final EventHandler<InputEvent> mMouseDragListener;
private final BooleanConsumer mRefreshLayoutEnabler;
private final BandController mBandController;
private final MouseDelegate mMouseDelegate = new MouseDelegate();
private final TouchDelegate mTouchDelegate = new TouchDelegate();
// Currently only initialized on IS_DEBUGGABLE builds.
private final @Nullable ScaleGestureDetector mScaleDetector;
public ListeningGestureDetector(
Features features,
Context context,
RecyclerView recView,
EventHandler<InputEvent> mouseDragListener,
BooleanConsumer refreshLayoutEnabler,
GestureSelector gestureSelector,
UserInputHandler<? extends InputEvent> handler,
@Nullable BandController bandController,
Consumer<Float> scaleHandler) {
super(context, handler);
mFeatures = features;
mMouseDragListener = mouseDragListener;
mRefreshLayoutEnabler = refreshLayoutEnabler;
mGestureSelector = gestureSelector;
mBandController = bandController;
recView.addOnItemTouchListener(this);
mScaleDetector = !Build.IS_DEBUGGABLE
? null
: new ScaleGestureDetector(
context,
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (VERBOSE) Log.v(TAG,
"Received scale event: " + detector.getScaleFactor());
scaleHandler.accept(detector.getScaleFactor());
return true;
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
boolean handled = false;
// TODO: Re-wire event handling so that we're not dispatching
// events to to scaledetector's #onTouchEvent from this
// #onInterceptTouchEvent touch event.
if (mFeatures.isGestureScaleEnabled()
&& mScaleDetector != null) {
mScaleDetector.onTouchEvent(e);
}
try (InputEvent event = MotionInputEvent.obtain(e, rv)) {
if (event.isMouseEvent()) {
if (event.isActionDown()) {
mRefreshLayoutEnabler.accept(false);
}
handled |= mMouseDelegate.onInterceptTouchEvent(event);
} else {
// If user has started some gesture while RecyclerView is not at the top, disable
// refresh
if (event.isActionDown() && rv.computeVerticalScrollOffset() != 0) {
mRefreshLayoutEnabler.accept(false);
}
handled |= mTouchDelegate.onInterceptTouchEvent(event);
}
if (event.isActionUp()) {
mRefreshLayoutEnabler.accept(true);
}
}
// Forward all events to UserInputHandler.
// This is necessary since UserInputHandler needs to always see the first DOWN event. Or
// else all future UP events will be tossed.
handled |= onTouchEvent(e);
return handled;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
try (InputEvent event = MotionInputEvent.obtain(e, rv)) {
if (Events.isMouseEvent(e)) {
mMouseDelegate.onTouchEvent(event);
} else {
mTouchDelegate.onTouchEvent(rv, event);
}
if (event.isActionUp()) {
mRefreshLayoutEnabler.accept(true);
}
}
// Note: even though this event is being handled as part of gestures such as drag and band,
// continue forwarding to the GestureDetector. The detector needs to see the entire cluster
// of events in order to properly interpret other gestures, such as long press.
onTouchEvent(e);
}
private class MouseDelegate {
boolean onInterceptTouchEvent(InputEvent e) {
if (Events.isMouseDragEvent(e)) {
return mMouseDragListener.accept(e);
} else if (mBandController != null &&
(mBandController.shouldStart(e) || mBandController.shouldStop(e))) {
return mBandController.onInterceptTouchEvent(e);
}
return false;
}
void onTouchEvent(InputEvent e) {
if (mBandController != null) {
mBandController.onTouchEvent(e);
}
}
}
private class TouchDelegate {
boolean onInterceptTouchEvent(InputEvent e) {
// Gesture Selector needs to be constantly fed events, so that when a long press does
// happen, we would have the last DOWN event that occurred to keep track of our anchor
// point
return mGestureSelector.onInterceptTouchEvent(e);
}
// TODO: Make this take just an InputEvent, no RecyclerView
void onTouchEvent(RecyclerView rv, InputEvent e) {
mGestureSelector.onTouchEvent(rv, e);
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
}