1 /* 2 * Copyright (C) 2022 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 18 package com.android.systemui.statusbar.gesture 19 20 import android.annotation.CallSuper 21 import android.os.Looper 22 import android.view.Choreographer 23 import android.view.InputEvent 24 import android.view.MotionEvent 25 import com.android.systemui.settings.DisplayTracker 26 import com.android.systemui.shared.system.InputChannelCompat 27 import com.android.systemui.shared.system.InputMonitorCompat 28 29 /** 30 * An abstract class to help detect gestures that occur anywhere on the display (not specific to a 31 * certain view). 32 * 33 * This class handles starting/stopping the gesture detection system as well as 34 * registering/unregistering callbacks for when gestures occur. Note that the class will only listen 35 * for gestures when there's at least one callback registered. 36 * 37 * Subclasses should implement [onInputEvent] to detect their specific gesture. Once a specific 38 * gesture is detected, they should call [onGestureDetected] (which will notify the callbacks). 39 */ 40 abstract class GenericGestureDetector( 41 private val tag: String, 42 private val displayTracker: DisplayTracker 43 ) { 44 /** 45 * Active callbacks, each associated with a tag. Gestures will only be monitored if 46 * [callbacks.size] > 0. 47 */ 48 private val callbacks: MutableMap<String, (MotionEvent) -> Unit> = mutableMapOf() 49 50 private var inputMonitor: InputMonitorCompat? = null 51 private var inputReceiver: InputChannelCompat.InputEventReceiver? = null 52 53 /** 54 * Adds a callback that will be triggered when the tap gesture is detected. 55 * 56 * The callback receive the last motion event in the gesture. 57 */ addOnGestureDetectedCallbacknull58 fun addOnGestureDetectedCallback(tag: String, callback: (MotionEvent) -> Unit) { 59 val callbacksWasEmpty = callbacks.isEmpty() 60 callbacks[tag] = callback 61 if (callbacksWasEmpty) { 62 startGestureListening() 63 } 64 } 65 66 /** Removes the callback. */ removeOnGestureDetectedCallbacknull67 fun removeOnGestureDetectedCallback(tag: String) { 68 callbacks.remove(tag) 69 if (callbacks.isEmpty()) { 70 stopGestureListening() 71 } 72 } 73 74 /** Triggered each time a touch event occurs (and at least one callback is registered). */ onInputEventnull75 abstract fun onInputEvent(ev: InputEvent) 76 77 /** 78 * Should be called by subclasses when their specific gesture is detected with the last motion 79 * event in the gesture. 80 */ 81 internal fun onGestureDetected(e: MotionEvent) { 82 callbacks.values.forEach { it.invoke(e) } 83 } 84 85 /** Start listening to touch events. */ 86 @CallSuper startGestureListeningnull87 internal open fun startGestureListening() { 88 stopGestureListening() 89 90 inputMonitor = InputMonitorCompat(tag, displayTracker.defaultDisplayId).also { 91 inputReceiver = it.getInputReceiver( 92 Looper.getMainLooper(), 93 Choreographer.getInstance(), 94 this::onInputEvent 95 ) 96 } 97 } 98 99 /** Stop listening to touch events. */ 100 @CallSuper stopGestureListeningnull101 internal open fun stopGestureListening() { 102 inputMonitor?.let { 103 inputMonitor = null 104 it.dispose() 105 } 106 inputReceiver?.let { 107 inputReceiver = null 108 it.dispose() 109 } 110 } 111 } 112