Skip to content

Commit f113487

Browse files
committed
API for accessing hand gestures
1 parent 549e302 commit f113487

File tree

11 files changed

+227
-18
lines changed

11 files changed

+227
-18
lines changed

bridge-core/src/main/java/com/penumbraos/bridge/BridgeService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class BridgeService {
1616

1717
private var touchpadProvider: ITouchpadProvider? = null
1818
private var ledProvider: ILedProvider? = null
19+
private var handGestureProvider: IHandGestureProvider? = null
1920
private var handTrackingProvider: IHandTrackingProvider? = null
2021

2122
private var esimProvider: IEsimProvider? = null
@@ -48,6 +49,10 @@ class BridgeService {
4849
return this@BridgeService.ledProvider?.asBinder()
4950
}
5051

52+
override fun getHandGestureProvider(): IBinder? {
53+
return this@BridgeService.handGestureProvider?.asBinder()
54+
}
55+
5156
override fun getHandTrackingProvider(): IBinder? {
5257
return this@BridgeService.handTrackingProvider?.asBinder()
5358
}
@@ -71,6 +76,7 @@ class BridgeService {
7176
sttProvider: ISttProvider?,
7277
touchpadProvider: ITouchpadProvider?,
7378
ledProvider: ILedProvider?,
79+
handGestureProvider: IHandGestureProvider?,
7480
handTrackingProvider: IHandTrackingProvider?,
7581
esimProvider: IEsimProvider?
7682
) {
@@ -83,6 +89,7 @@ class BridgeService {
8389

8490
this@BridgeService.touchpadProvider = touchpadProvider
8591
this@BridgeService.ledProvider = ledProvider
92+
this@BridgeService.handGestureProvider = handGestureProvider
8693
this@BridgeService.handTrackingProvider = handTrackingProvider
8794

8895
this@BridgeService.esimProvider = esimProvider

bridge-shared/aidl/com/penumbraos/bridge/IBridge.aidl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.penumbraos.bridge.IDnsProvider;
66
import com.penumbraos.bridge.ISttProvider;
77
import com.penumbraos.bridge.ITouchpadProvider;
88
import com.penumbraos.bridge.ILedProvider;
9+
import com.penumbraos.bridge.IHandGestureProvider;
910
import com.penumbraos.bridge.IHandTrackingProvider;
1011
import com.penumbraos.bridge.IEsimProvider;
1112
import com.penumbraos.bridge.ISettingsProvider;
@@ -20,13 +21,14 @@ interface IBridge {
2021

2122
IBinder getTouchpadProvider();
2223
IBinder getLedProvider();
24+
IBinder getHandGestureProvider();
2325
IBinder getHandTrackingProvider();
2426

2527
IBinder getEsimProvider();
2628

2729
IBinder getSettingsProvider();
2830
IBinder getShellProvider();
29-
void registerSystemService(IHttpProvider httpProvider, IWebSocketProvider webSocketProvider, IDnsProvider dnsProvider, ISttProvider sttProvider, ITouchpadProvider touchpadProvider, ILedProvider ledProvider, IHandTrackingProvider handTrackingProvider, IEsimProvider esimProvider);
31+
void registerSystemService(IHttpProvider httpProvider, IWebSocketProvider webSocketProvider, IDnsProvider dnsProvider, ISttProvider sttProvider, ITouchpadProvider touchpadProvider, ILedProvider ledProvider, IHandGestureProvider handGestureProvider, IHandTrackingProvider handTrackingProvider, IEsimProvider esimProvider);
3032
void registerSettingsService(ISettingsProvider settingsProvider);
3133
void registerShellService(IShellProvider shellProvider);
3234
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.penumbraos.bridge;
2+
3+
import com.penumbraos.bridge.callback.IHandGestureCallback;
4+
5+
interface IHandGestureProvider {
6+
void registerCallback(IHandGestureCallback callback);
7+
void deregisterCallback(IHandGestureCallback callback);
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.penumbraos.bridge.callback;
2+
3+
oneway interface IHandGestureCallback {
4+
void onHandClose();
5+
void onHandPush();
6+
}

bridge-system/src/main/java/com/penumbraos/bridge_system/Entrypoint.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.penumbraos.bridge_system.esim.LPA_APK_PATH
1111
import com.penumbraos.bridge_system.esim.MockFactoryService
1212
import com.penumbraos.bridge_system.provider.DnsProvider
1313
import com.penumbraos.bridge_system.provider.EsimProvider
14+
import com.penumbraos.bridge_system.provider.HandGestureProvider
1415
import com.penumbraos.bridge_system.provider.HandTrackingProvider
1516
import com.penumbraos.bridge_system.provider.HttpProvider
1617
import com.penumbraos.bridge_system.provider.LedProvider
@@ -71,6 +72,7 @@ class Entrypoint {
7172
SttProvider(context, looper),
7273
TouchpadProvider(looper),
7374
LedProvider(context),
75+
HandGestureProvider(looper),
7476
HandTrackingProvider(context),
7577
EsimProvider(classLoader, context)
7678
)
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.penumbraos.bridge_system.provider
2+
3+
import android.os.Looper
4+
import android.util.Log
5+
import android.view.InputChannel
6+
import android.view.InputEvent
7+
import android.view.InputEventReceiver
8+
import android.view.KeyEvent
9+
import android.view.MotionEvent
10+
import com.penumbraos.bridge.IHandGestureProvider
11+
import com.penumbraos.bridge.callback.IHandGestureCallback
12+
import com.penumbraos.bridge_system.util.registerTouchpadInputChannel
13+
14+
private const val TAG = "HandGestureProvider"
15+
16+
private const val VELOCITY_THRESHOLD = 0.5f
17+
private const val VELOCITY_MIN_MOVING = 0.3f
18+
private const val GESTURE_COOLDOWN_MS = 500L
19+
20+
class HandGestureProvider(private val looper: Looper) : IHandGestureProvider.Stub() {
21+
private val callbacks = mutableListOf<IHandGestureCallback>()
22+
private var listener: EventListener? = null
23+
24+
inner class PushListener {
25+
26+
private var gestureActive = false
27+
28+
private var lastDepth = Float.NaN
29+
private var lastTimestamp = 0L
30+
private var gestureEndTime = 0L
31+
32+
fun processMotionEvent(event: MotionEvent) {
33+
val depth = event.getAxisValue(MotionEvent.AXIS_PRESSURE)
34+
val timestamp = event.eventTime
35+
36+
if (lastDepth.isNaN()) {
37+
lastDepth = depth
38+
lastTimestamp = timestamp
39+
return
40+
}
41+
42+
val timeDelta = timestamp - lastTimestamp
43+
if (timeDelta > 0) {
44+
val depthDelta = depth - lastDepth
45+
val velocity = kotlin.math.abs(depthDelta * 1000.0f / timeDelta)
46+
47+
when {
48+
!gestureActive && velocity >= VELOCITY_THRESHOLD && (timestamp - gestureEndTime) > GESTURE_COOLDOWN_MS -> {
49+
gestureActive = true
50+
onHandPush()
51+
}
52+
53+
gestureActive && velocity < VELOCITY_MIN_MOVING -> {
54+
gestureActive = false
55+
gestureEndTime = timestamp
56+
}
57+
}
58+
}
59+
60+
lastDepth = depth
61+
lastTimestamp = timestamp
62+
}
63+
}
64+
65+
inner class EventListener(inputChannel: InputChannel) :
66+
InputEventReceiver(inputChannel, looper) {
67+
private val pushListener = PushListener()
68+
69+
override fun onInputEvent(event: InputEvent?) {
70+
if (event != null) {
71+
if (event is MotionEvent) {
72+
pushListener.processMotionEvent(event)
73+
} else if (event is KeyEvent && event.keyCode == KeyEvent.KEYCODE_H) {
74+
onHandClose()
75+
}
76+
}
77+
super.onInputEvent(event)
78+
}
79+
}
80+
81+
override fun registerCallback(callback: IHandGestureCallback) {
82+
callbacks.add(callback)
83+
registerListenerIfNecessary()
84+
}
85+
86+
override fun deregisterCallback(callback: IHandGestureCallback) {
87+
callbacks.remove(callback)
88+
if (callbacks.count() < 1) {
89+
Log.w(TAG, "Deregistering hand gesture listener")
90+
listener?.dispose()
91+
listener = null
92+
}
93+
}
94+
95+
fun onHandPush() {
96+
callCallback { callback -> callback.onHandPush() }
97+
}
98+
99+
fun onHandClose() {
100+
callCallback { callback -> callback.onHandClose() }
101+
}
102+
103+
private fun callCallback(withCallback: (IHandGestureCallback) -> Unit) {
104+
val callbacksToRemove = mutableListOf<IHandGestureCallback>()
105+
callbacks.forEach { callback ->
106+
safeCallback(TAG, {
107+
withCallback(callback)
108+
}, onDeadObject = {
109+
callbacksToRemove.add(callback)
110+
})
111+
}
112+
callbacksToRemove.forEach { callback -> deregisterCallback(callback) }
113+
}
114+
115+
private fun registerListenerIfNecessary() {
116+
if (listener != null) {
117+
return
118+
}
119+
120+
Log.w(TAG, "Registering touchpad listener")
121+
122+
val inputChannel = registerTouchpadInputChannel(TAG)
123+
124+
if (inputChannel != null) {
125+
listener = EventListener(inputChannel)
126+
}
127+
}
128+
}

bridge-system/src/main/java/com/penumbraos/bridge_system/provider/TouchpadProvider.kt

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
package com.penumbraos.bridge_system.provider
22

3-
import android.hardware.input.IInputManager
43
import android.os.Looper
5-
import android.os.ServiceManager
64
import android.util.Log
75
import android.view.InputChannel
86
import android.view.InputEvent
97
import android.view.InputEventReceiver
108
import com.penumbraos.bridge.ITouchpadProvider
119
import com.penumbraos.bridge.callback.ITouchpadCallback
10+
import com.penumbraos.bridge_system.util.registerTouchpadInputChannel
1211

13-
private const val TOUCHPAD_MONITOR_NAME = "Humane Touchpad Monitor"
14-
private const val TOUCHPAD_DISPLAY_ID = 3344
1512
private const val TOUCHPAD_EVENT_SOURCE = 0x100008
1613

1714
private const val TAG = "TouchpadProvider"
1815

1916
class TouchpadProvider(private val looper: Looper) :
2017
ITouchpadProvider.Stub() {
2118
private val callbacks = mutableListOf<ITouchpadCallback>()
19+
private var listener: EventListener? = null
2220

2321
inner class EventListener(inputChannel: InputChannel) :
2422
InputEventReceiver(inputChannel, looper) {
2523
override fun onInputEvent(event: InputEvent?) {
26-
if (event != null) {
24+
if (event != null && event.isFromSource(TOUCHPAD_EVENT_SOURCE)) {
2725
val callbacksToRemove = mutableListOf<ITouchpadCallback>()
2826
callbacks.forEach { callback ->
2927
safeCallback(TAG, {
@@ -38,8 +36,6 @@ class TouchpadProvider(private val looper: Looper) :
3836
}
3937
}
4038

41-
private var listener: EventListener? = null
42-
4339
override fun registerCallback(callback: ITouchpadCallback) {
4440
callbacks.add(callback)
4541
registerListenerIfNecessary()
@@ -61,15 +57,10 @@ class TouchpadProvider(private val looper: Looper) :
6157

6258
Log.w(TAG, "Registering touchpad listener")
6359

64-
try {
65-
val inputManagerBinder = ServiceManager.getService("input")
66-
val inputManager = IInputManager.Stub.asInterface(inputManagerBinder)
60+
val inputChannel = registerTouchpadInputChannel(TAG)
6761

68-
val inputMonitor =
69-
inputManager.monitorGestureInput(TOUCHPAD_MONITOR_NAME, TOUCHPAD_DISPLAY_ID)
70-
listener = EventListener(inputMonitor.inputChannel)
71-
} catch (e: Exception) {
72-
Log.e(TAG, "Failed to register touchpad listener", e)
62+
if (inputChannel != null) {
63+
listener = EventListener(inputChannel)
7364
}
7465
}
7566
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.penumbraos.bridge_system.util
2+
3+
import android.hardware.input.IInputManager
4+
import android.os.ServiceManager
5+
import android.util.Log
6+
import android.view.InputChannel
7+
8+
private const val TOUCHPAD_MONITOR_NAME = "Humane Touchpad Monitor"
9+
private const val TOUCHPAD_DISPLAY_ID = 3344
10+
11+
fun registerTouchpadInputChannel(tag: String): InputChannel? {
12+
return try {
13+
val inputManagerBinder = ServiceManager.getService("input")
14+
val inputManager = IInputManager.Stub.asInterface(inputManagerBinder)
15+
16+
inputManager.monitorGestureInput(TOUCHPAD_MONITOR_NAME, TOUCHPAD_DISPLAY_ID).inputChannel
17+
} catch (e: Exception) {
18+
Log.e(tag, "Failed to register touchpad listener", e)
19+
null
20+
}
21+
}

sdk/src/main/java/com/penumbraos/sdk/PenumbraClient.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import android.os.IBinder
99
import android.util.Log
1010
import com.penumbraos.bridge.IBridge
1111
import com.penumbraos.bridge.IDnsProvider
12+
import com.penumbraos.bridge.IEsimProvider
13+
import com.penumbraos.bridge.IHandGestureProvider
1214
import com.penumbraos.bridge.IHandTrackingProvider
1315
import com.penumbraos.bridge.IHttpProvider
1416
import com.penumbraos.bridge.ILedProvider
@@ -17,10 +19,10 @@ import com.penumbraos.bridge.IShellProvider
1719
import com.penumbraos.bridge.ISttProvider
1820
import com.penumbraos.bridge.ITouchpadProvider
1921
import com.penumbraos.bridge.IWebSocketProvider
20-
import com.penumbraos.bridge.IEsimProvider
2122
import com.penumbraos.bridge.external.BRIDGE_SERVICE_READY
2223
import com.penumbraos.sdk.api.DnsClient
2324
import com.penumbraos.sdk.api.EsimClient
25+
import com.penumbraos.sdk.api.HandGestureClient
2426
import com.penumbraos.sdk.api.HandTrackingClient
2527
import com.penumbraos.sdk.api.HttpClient
2628
import com.penumbraos.sdk.api.LedClient
@@ -49,6 +51,7 @@ class PenumbraClient {
4951

5052
lateinit var touchpad: TouchpadClient
5153
lateinit var led: LedClient
54+
lateinit var handGesture: HandGestureClient
5255
lateinit var handTracking: HandTrackingClient
5356

5457
lateinit var esim: EsimClient
@@ -125,10 +128,12 @@ class PenumbraClient {
125128
val touchpadProvider =
126129
ITouchpadProvider.Stub.asInterface(service!!.getTouchpadProvider())
127130
val ledProvider = ILedProvider.Stub.asInterface(service!!.getLedProvider())
131+
val handGestureProvider =
132+
IHandGestureProvider.Stub.asInterface(service!!.getHandGestureProvider())
128133
val handTrackingProvider =
129134
IHandTrackingProvider.Stub.asInterface(service!!.getHandTrackingProvider())
130135

131-
val esimProvider =
136+
val esimProvider =
132137
IEsimProvider.Stub.asInterface(service!!.getEsimProvider())
133138

134139
val settingsProvider =
@@ -145,6 +150,7 @@ class PenumbraClient {
145150

146151
touchpad = TouchpadClient(touchpadProvider)
147152
led = LedClient(ledProvider)
153+
handGesture = HandGestureClient(handGestureProvider)
148154
handTracking = HandTrackingClient(handTrackingProvider)
149155

150156
esim = EsimClient(esimProvider)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.penumbraos.sdk.api
2+
3+
import com.penumbraos.bridge.IHandGestureProvider
4+
import com.penumbraos.bridge.callback.IHandGestureCallback
5+
import com.penumbraos.sdk.api.types.HandGestureReceiver
6+
import java.util.concurrent.ConcurrentHashMap
7+
8+
class HandGestureClient(private val handGestureProvider: IHandGestureProvider) {
9+
private val registeredCallbacks =
10+
ConcurrentHashMap<HandGestureReceiver, IHandGestureCallback.Stub>()
11+
12+
fun register(receiver: HandGestureReceiver) {
13+
val callbackStub = object : IHandGestureCallback.Stub() {
14+
override fun onHandClose() {
15+
receiver.onHandClose()
16+
}
17+
18+
override fun onHandPush() {
19+
receiver.onHandPush()
20+
}
21+
}
22+
registeredCallbacks[receiver] = callbackStub
23+
handGestureProvider.registerCallback(callbackStub)
24+
}
25+
26+
fun remove(receiver: HandGestureReceiver) {
27+
val callbackStub = registeredCallbacks.remove(receiver)
28+
if (callbackStub != null) {
29+
handGestureProvider.deregisterCallback(callbackStub)
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)