/* * 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.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, private val displayId: Int, ) { /** * Active callbacks, each associated with a tag. Gestures will only be monitored if * [callbacks.size] > 0. */ private val callbacks: MutableMap 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, displayId).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() } } }