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