blob: 3a4731a5a6aa3e5141294fbdf59a3decd028070a [file] [log] [blame]
/*
* Copyright (C) 2022 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.systemui.statusbar.gesture
import android.annotation.CallSuper
import android.os.Looper
import android.view.Choreographer
import android.view.Display
import android.view.InputEvent
import android.view.MotionEvent
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.shared.system.InputMonitorCompat
/**
* An abstract class to help detect gestures that occur anywhere on the display (not specific to a
* certain view).
*
* This class handles starting/stopping the gesture detection system as well as
* registering/unregistering callbacks for when gestures occur. Note that the class will only listen
* for gestures when there's at least one callback registered.
*
* Subclasses should implement [onInputEvent] to detect their specific gesture. Once a specific
* gesture is detected, they should call [onGestureDetected] (which will notify the callbacks).
*/
abstract class GenericGestureDetector(
private val tag: String
) {
/**
* Active callbacks, each associated with a tag. Gestures will only be monitored if
* [callbacks.size] > 0.
*/
private val callbacks: MutableMap<String, (MotionEvent) -> Unit> = mutableMapOf()
private var inputMonitor: InputMonitorCompat? = null
private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
/**
* Adds a callback that will be triggered when the tap gesture is detected.
*
* The callback receive the last motion event in the gesture.
*/
fun addOnGestureDetectedCallback(tag: String, callback: (MotionEvent) -> Unit) {
val callbacksWasEmpty = callbacks.isEmpty()
callbacks[tag] = callback
if (callbacksWasEmpty) {
startGestureListening()
}
}
/** Removes the callback. */
fun removeOnGestureDetectedCallback(tag: String) {
callbacks.remove(tag)
if (callbacks.isEmpty()) {
stopGestureListening()
}
}
/** Triggered each time a touch event occurs (and at least one callback is registered). */
abstract fun onInputEvent(ev: InputEvent)
/**
* Should be called by subclasses when their specific gesture is detected with the last motion
* event in the gesture.
*/
internal fun onGestureDetected(e: MotionEvent) {
callbacks.values.forEach { it.invoke(e) }
}
/** Start listening to touch events. */
@CallSuper
internal open fun startGestureListening() {
stopGestureListening()
inputMonitor = InputMonitorCompat(tag, Display.DEFAULT_DISPLAY).also {
inputReceiver = it.getInputReceiver(
Looper.getMainLooper(),
Choreographer.getInstance(),
this::onInputEvent
)
}
}
/** Stop listening to touch events. */
@CallSuper
internal open fun stopGestureListening() {
inputMonitor?.let {
inputMonitor = null
it.dispose()
}
inputReceiver?.let {
inputReceiver = null
it.dispose()
}
}
}