API for accessing hand gestures

This commit is contained in:
Adam Gastineau 2025-08-15 10:22:29 -07:00
parent 549e30278a
commit f113487937
11 changed files with 227 additions and 18 deletions

View File

@ -16,6 +16,7 @@ class BridgeService {
private var touchpadProvider: ITouchpadProvider? = null
private var ledProvider: ILedProvider? = null
private var handGestureProvider: IHandGestureProvider? = null
private var handTrackingProvider: IHandTrackingProvider? = null
private var esimProvider: IEsimProvider? = null
@ -48,6 +49,10 @@ class BridgeService {
return this@BridgeService.ledProvider?.asBinder()
}
override fun getHandGestureProvider(): IBinder? {
return this@BridgeService.handGestureProvider?.asBinder()
}
override fun getHandTrackingProvider(): IBinder? {
return this@BridgeService.handTrackingProvider?.asBinder()
}
@ -71,6 +76,7 @@ class BridgeService {
sttProvider: ISttProvider?,
touchpadProvider: ITouchpadProvider?,
ledProvider: ILedProvider?,
handGestureProvider: IHandGestureProvider?,
handTrackingProvider: IHandTrackingProvider?,
esimProvider: IEsimProvider?
) {
@ -83,6 +89,7 @@ class BridgeService {
this@BridgeService.touchpadProvider = touchpadProvider
this@BridgeService.ledProvider = ledProvider
this@BridgeService.handGestureProvider = handGestureProvider
this@BridgeService.handTrackingProvider = handTrackingProvider
this@BridgeService.esimProvider = esimProvider

View File

@ -6,6 +6,7 @@ import com.penumbraos.bridge.IDnsProvider;
import com.penumbraos.bridge.ISttProvider;
import com.penumbraos.bridge.ITouchpadProvider;
import com.penumbraos.bridge.ILedProvider;
import com.penumbraos.bridge.IHandGestureProvider;
import com.penumbraos.bridge.IHandTrackingProvider;
import com.penumbraos.bridge.IEsimProvider;
import com.penumbraos.bridge.ISettingsProvider;
@ -20,13 +21,14 @@ interface IBridge {
IBinder getTouchpadProvider();
IBinder getLedProvider();
IBinder getHandGestureProvider();
IBinder getHandTrackingProvider();
IBinder getEsimProvider();
IBinder getSettingsProvider();
IBinder getShellProvider();
void registerSystemService(IHttpProvider httpProvider, IWebSocketProvider webSocketProvider, IDnsProvider dnsProvider, ISttProvider sttProvider, ITouchpadProvider touchpadProvider, ILedProvider ledProvider, IHandTrackingProvider handTrackingProvider, IEsimProvider esimProvider);
void registerSystemService(IHttpProvider httpProvider, IWebSocketProvider webSocketProvider, IDnsProvider dnsProvider, ISttProvider sttProvider, ITouchpadProvider touchpadProvider, ILedProvider ledProvider, IHandGestureProvider handGestureProvider, IHandTrackingProvider handTrackingProvider, IEsimProvider esimProvider);
void registerSettingsService(ISettingsProvider settingsProvider);
void registerShellService(IShellProvider shellProvider);
}

View File

@ -0,0 +1,8 @@
package com.penumbraos.bridge;
import com.penumbraos.bridge.callback.IHandGestureCallback;
interface IHandGestureProvider {
void registerCallback(IHandGestureCallback callback);
void deregisterCallback(IHandGestureCallback callback);
}

View File

@ -0,0 +1,6 @@
package com.penumbraos.bridge.callback;
oneway interface IHandGestureCallback {
void onHandClose();
void onHandPush();
}

View File

@ -11,6 +11,7 @@ import com.penumbraos.bridge_system.esim.LPA_APK_PATH
import com.penumbraos.bridge_system.esim.MockFactoryService
import com.penumbraos.bridge_system.provider.DnsProvider
import com.penumbraos.bridge_system.provider.EsimProvider
import com.penumbraos.bridge_system.provider.HandGestureProvider
import com.penumbraos.bridge_system.provider.HandTrackingProvider
import com.penumbraos.bridge_system.provider.HttpProvider
import com.penumbraos.bridge_system.provider.LedProvider
@ -71,6 +72,7 @@ class Entrypoint {
SttProvider(context, looper),
TouchpadProvider(looper),
LedProvider(context),
HandGestureProvider(looper),
HandTrackingProvider(context),
EsimProvider(classLoader, context)
)

View File

@ -0,0 +1,128 @@
package com.penumbraos.bridge_system.provider
import android.os.Looper
import android.util.Log
import android.view.InputChannel
import android.view.InputEvent
import android.view.InputEventReceiver
import android.view.KeyEvent
import android.view.MotionEvent
import com.penumbraos.bridge.IHandGestureProvider
import com.penumbraos.bridge.callback.IHandGestureCallback
import com.penumbraos.bridge_system.util.registerTouchpadInputChannel
private const val TAG = "HandGestureProvider"
private const val VELOCITY_THRESHOLD = 0.5f
private const val VELOCITY_MIN_MOVING = 0.3f
private const val GESTURE_COOLDOWN_MS = 500L
class HandGestureProvider(private val looper: Looper) : IHandGestureProvider.Stub() {
private val callbacks = mutableListOf<IHandGestureCallback>()
private var listener: EventListener? = null
inner class PushListener {
private var gestureActive = false
private var lastDepth = Float.NaN
private var lastTimestamp = 0L
private var gestureEndTime = 0L
fun processMotionEvent(event: MotionEvent) {
val depth = event.getAxisValue(MotionEvent.AXIS_PRESSURE)
val timestamp = event.eventTime
if (lastDepth.isNaN()) {
lastDepth = depth
lastTimestamp = timestamp
return
}
val timeDelta = timestamp - lastTimestamp
if (timeDelta > 0) {
val depthDelta = depth - lastDepth
val velocity = kotlin.math.abs(depthDelta * 1000.0f / timeDelta)
when {
!gestureActive && velocity >= VELOCITY_THRESHOLD && (timestamp - gestureEndTime) > GESTURE_COOLDOWN_MS -> {
gestureActive = true
onHandPush()
}
gestureActive && velocity < VELOCITY_MIN_MOVING -> {
gestureActive = false
gestureEndTime = timestamp
}
}
}
lastDepth = depth
lastTimestamp = timestamp
}
}
inner class EventListener(inputChannel: InputChannel) :
InputEventReceiver(inputChannel, looper) {
private val pushListener = PushListener()
override fun onInputEvent(event: InputEvent?) {
if (event != null) {
if (event is MotionEvent) {
pushListener.processMotionEvent(event)
} else if (event is KeyEvent && event.keyCode == KeyEvent.KEYCODE_H) {
onHandClose()
}
}
super.onInputEvent(event)
}
}
override fun registerCallback(callback: IHandGestureCallback) {
callbacks.add(callback)
registerListenerIfNecessary()
}
override fun deregisterCallback(callback: IHandGestureCallback) {
callbacks.remove(callback)
if (callbacks.count() < 1) {
Log.w(TAG, "Deregistering hand gesture listener")
listener?.dispose()
listener = null
}
}
fun onHandPush() {
callCallback { callback -> callback.onHandPush() }
}
fun onHandClose() {
callCallback { callback -> callback.onHandClose() }
}
private fun callCallback(withCallback: (IHandGestureCallback) -> Unit) {
val callbacksToRemove = mutableListOf<IHandGestureCallback>()
callbacks.forEach { callback ->
safeCallback(TAG, {
withCallback(callback)
}, onDeadObject = {
callbacksToRemove.add(callback)
})
}
callbacksToRemove.forEach { callback -> deregisterCallback(callback) }
}
private fun registerListenerIfNecessary() {
if (listener != null) {
return
}
Log.w(TAG, "Registering touchpad listener")
val inputChannel = registerTouchpadInputChannel(TAG)
if (inputChannel != null) {
listener = EventListener(inputChannel)
}
}
}

View File

@ -1,17 +1,14 @@
package com.penumbraos.bridge_system.provider
import android.hardware.input.IInputManager
import android.os.Looper
import android.os.ServiceManager
import android.util.Log
import android.view.InputChannel
import android.view.InputEvent
import android.view.InputEventReceiver
import com.penumbraos.bridge.ITouchpadProvider
import com.penumbraos.bridge.callback.ITouchpadCallback
import com.penumbraos.bridge_system.util.registerTouchpadInputChannel
private const val TOUCHPAD_MONITOR_NAME = "Humane Touchpad Monitor"
private const val TOUCHPAD_DISPLAY_ID = 3344
private const val TOUCHPAD_EVENT_SOURCE = 0x100008
private const val TAG = "TouchpadProvider"
@ -19,11 +16,12 @@ private const val TAG = "TouchpadProvider"
class TouchpadProvider(private val looper: Looper) :
ITouchpadProvider.Stub() {
private val callbacks = mutableListOf<ITouchpadCallback>()
private var listener: EventListener? = null
inner class EventListener(inputChannel: InputChannel) :
InputEventReceiver(inputChannel, looper) {
override fun onInputEvent(event: InputEvent?) {
if (event != null) {
if (event != null && event.isFromSource(TOUCHPAD_EVENT_SOURCE)) {
val callbacksToRemove = mutableListOf<ITouchpadCallback>()
callbacks.forEach { callback ->
safeCallback(TAG, {
@ -38,8 +36,6 @@ class TouchpadProvider(private val looper: Looper) :
}
}
private var listener: EventListener? = null
override fun registerCallback(callback: ITouchpadCallback) {
callbacks.add(callback)
registerListenerIfNecessary()
@ -61,15 +57,10 @@ class TouchpadProvider(private val looper: Looper) :
Log.w(TAG, "Registering touchpad listener")
try {
val inputManagerBinder = ServiceManager.getService("input")
val inputManager = IInputManager.Stub.asInterface(inputManagerBinder)
val inputChannel = registerTouchpadInputChannel(TAG)
val inputMonitor =
inputManager.monitorGestureInput(TOUCHPAD_MONITOR_NAME, TOUCHPAD_DISPLAY_ID)
listener = EventListener(inputMonitor.inputChannel)
} catch (e: Exception) {
Log.e(TAG, "Failed to register touchpad listener", e)
if (inputChannel != null) {
listener = EventListener(inputChannel)
}
}
}

View File

@ -0,0 +1,21 @@
package com.penumbraos.bridge_system.util
import android.hardware.input.IInputManager
import android.os.ServiceManager
import android.util.Log
import android.view.InputChannel
private const val TOUCHPAD_MONITOR_NAME = "Humane Touchpad Monitor"
private const val TOUCHPAD_DISPLAY_ID = 3344
fun registerTouchpadInputChannel(tag: String): InputChannel? {
return try {
val inputManagerBinder = ServiceManager.getService("input")
val inputManager = IInputManager.Stub.asInterface(inputManagerBinder)
inputManager.monitorGestureInput(TOUCHPAD_MONITOR_NAME, TOUCHPAD_DISPLAY_ID).inputChannel
} catch (e: Exception) {
Log.e(tag, "Failed to register touchpad listener", e)
null
}
}

View File

@ -9,6 +9,8 @@ import android.os.IBinder
import android.util.Log
import com.penumbraos.bridge.IBridge
import com.penumbraos.bridge.IDnsProvider
import com.penumbraos.bridge.IEsimProvider
import com.penumbraos.bridge.IHandGestureProvider
import com.penumbraos.bridge.IHandTrackingProvider
import com.penumbraos.bridge.IHttpProvider
import com.penumbraos.bridge.ILedProvider
@ -17,10 +19,10 @@ import com.penumbraos.bridge.IShellProvider
import com.penumbraos.bridge.ISttProvider
import com.penumbraos.bridge.ITouchpadProvider
import com.penumbraos.bridge.IWebSocketProvider
import com.penumbraos.bridge.IEsimProvider
import com.penumbraos.bridge.external.BRIDGE_SERVICE_READY
import com.penumbraos.sdk.api.DnsClient
import com.penumbraos.sdk.api.EsimClient
import com.penumbraos.sdk.api.HandGestureClient
import com.penumbraos.sdk.api.HandTrackingClient
import com.penumbraos.sdk.api.HttpClient
import com.penumbraos.sdk.api.LedClient
@ -49,6 +51,7 @@ class PenumbraClient {
lateinit var touchpad: TouchpadClient
lateinit var led: LedClient
lateinit var handGesture: HandGestureClient
lateinit var handTracking: HandTrackingClient
lateinit var esim: EsimClient
@ -125,10 +128,12 @@ class PenumbraClient {
val touchpadProvider =
ITouchpadProvider.Stub.asInterface(service!!.getTouchpadProvider())
val ledProvider = ILedProvider.Stub.asInterface(service!!.getLedProvider())
val handGestureProvider =
IHandGestureProvider.Stub.asInterface(service!!.getHandGestureProvider())
val handTrackingProvider =
IHandTrackingProvider.Stub.asInterface(service!!.getHandTrackingProvider())
val esimProvider =
val esimProvider =
IEsimProvider.Stub.asInterface(service!!.getEsimProvider())
val settingsProvider =
@ -145,6 +150,7 @@ class PenumbraClient {
touchpad = TouchpadClient(touchpadProvider)
led = LedClient(ledProvider)
handGesture = HandGestureClient(handGestureProvider)
handTracking = HandTrackingClient(handTrackingProvider)
esim = EsimClient(esimProvider)

View File

@ -0,0 +1,32 @@
package com.penumbraos.sdk.api
import com.penumbraos.bridge.IHandGestureProvider
import com.penumbraos.bridge.callback.IHandGestureCallback
import com.penumbraos.sdk.api.types.HandGestureReceiver
import java.util.concurrent.ConcurrentHashMap
class HandGestureClient(private val handGestureProvider: IHandGestureProvider) {
private val registeredCallbacks =
ConcurrentHashMap<HandGestureReceiver, IHandGestureCallback.Stub>()
fun register(receiver: HandGestureReceiver) {
val callbackStub = object : IHandGestureCallback.Stub() {
override fun onHandClose() {
receiver.onHandClose()
}
override fun onHandPush() {
receiver.onHandPush()
}
}
registeredCallbacks[receiver] = callbackStub
handGestureProvider.registerCallback(callbackStub)
}
fun remove(receiver: HandGestureReceiver) {
val callbackStub = registeredCallbacks.remove(receiver)
if (callbackStub != null) {
handGestureProvider.deregisterCallback(callbackStub)
}
}
}

View File

@ -0,0 +1,6 @@
package com.penumbraos.sdk.api.types
interface HandGestureReceiver {
fun onHandClose()
fun onHandPush()
}