blob: 3d8f5173d25a59889062bd729360fcfab13b605f [file] [log] [blame]
/*
* Copyright (C) 2020 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.server.accessibility.magnification;
import android.annotation.MainThread;
import android.view.MotionEvent;
import com.android.server.accessibility.gestures.GestureMatcher;
import java.util.ArrayList;
import java.util.List;
/**
* A class to coordinates a series of individual {@link GestureMatcher} injecting in the constructor
* to triggers the appropriate callbacks on the {@link Listener} supplied. The event stream should
* start with {@link MotionEvent#ACTION_DOWN}, otherwise
* {@link Listener#onGestureCancelled(MotionEvent, MotionEvent, int)} will be triggered.
*
* @hide
*/
public final class GesturesObserver implements GestureMatcher.StateChangeListener {
/**
* Listeners to receive the result of gestures matching.
*/
public interface Listener {
/**
* Called when an event stream is recognized as a gesture.
*
* @param gestureId the gesture id of {@link GestureMatcher}.
* @param event The last event to determine the gesture. For the holding gestures,
* it's the last event before timeout.
* @param rawEvent The event without any transformations.
* @param policyFlags The policy flags of the most recent event.
*/
void onGestureCompleted(int gestureId, MotionEvent event, MotionEvent rawEvent,
int policyFlags);
/**
* Called when the system has decided an event stream doesn't match any known gesture or
* the first event is not {@link MotionEvent#ACTION_DOWN}.
*
* @param event The last event to determine the cancellation before timeout.
* @param rawEvent The event without any transformations.
* @param policyFlags The policy flags of the most recent event.
*/
void onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags);
}
private final List<GestureMatcher> mGestureMatchers = new ArrayList<>();
private final Listener mListener;
private boolean mObserveStarted = false;
private boolean mProcessMotionEvent = false;
private int mCancelledMatcherSize = 0;
public GesturesObserver(Listener listener, GestureMatcher... matchers) {
mListener = listener;
for (int i = 0; i < matchers.length; i++) {
matchers[i].setListener(this);
mGestureMatchers.add(matchers[i]);
}
}
/**
* Processes a motion event and attempts to match it to one of the gestures.
*
* @param event the event as passed in from the event stream.
* @param rawEvent the original un-modified event. Useful for calculating movements in physical
* space.
* @param policyFlags the policy flags as passed in from the event stream.
* @return {@code true} if one of the gesture is matched.
*/
@MainThread
public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (!mObserveStarted) {
if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
mListener.onGestureCancelled(event, rawEvent, policyFlags);
clear();
return false;
} else {
mObserveStarted = true;
}
}
mProcessMotionEvent = true;
for (int i = 0; i < mGestureMatchers.size(); i++) {
final GestureMatcher matcher = mGestureMatchers.get(i);
matcher.onMotionEvent(event, rawEvent, policyFlags);
if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
clear();
mProcessMotionEvent = false;
return true;
}
}
mProcessMotionEvent = false;
return false;
}
/**
* Clears all states to default.
*/
@MainThread
public void clear() {
for (GestureMatcher matcher : mGestureMatchers) {
matcher.clear();
}
mCancelledMatcherSize = 0;
mObserveStarted = false;
}
@Override
public void onStateChanged(int gestureId, int state, MotionEvent event,
MotionEvent rawEvent, int policyFlags) {
if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
mListener.onGestureCompleted(gestureId, event, rawEvent, policyFlags);
// Ideally we clear the states in onMotionEvent(), this case is for hold gestures.
// If we clear before processing up event , then MultiTap matcher cancels the gesture
// due to incorrect state. It ends up listener#onGestureCancelled is called even
// the gesture is detected.
if (!mProcessMotionEvent) {
clear();
}
} else if (state == GestureMatcher.STATE_GESTURE_CANCELED) {
mCancelledMatcherSize++;
if (mCancelledMatcherSize == mGestureMatchers.size()) {
mListener.onGestureCancelled(event, rawEvent, policyFlags);
clear();
}
}
}
}