rust-mobile / android-activity

Glue for building Rust applications on Android with NativeActivity or GameActivity
236 stars 46 forks source link

Expose android_app_set_motion_event_filter from native app glue #79

Open 4emodan opened 1 year ago

4emodan commented 1 year ago

By default android_native_app_glue uses the filter which only listens to SOURCE_TOUCHSCREEN (0x1002) events.

It doesn't seem to work on emulators - I saw event->source == 0x5002, can't even find a constant for that. The same problem I've experienced with the Samsung s-pen input devices.

So it would be great to have an option to setup the filter or drop it completely.

rib commented 1 year ago

Yeah, this relates to quite a significant difference between how NativeActivity based apps get events via an InputQueue and GameActivity instead hooks into Activity callbacks for events.

The InputQueue abstraction supports the notion that an event may not be "handled" by an application and in that case a default handler can run.

This is important for system buttons, like volume up/down + camera buttons which applications don't normally want to override.

The way this (roughly) works is that input events are dispatched to the root View which internally implements a notion of an 'InputStage' + 'AsyncInputStage' which are daisy chained together and allow events to be associated with an 'finish' handler. Two of these stages support forwarding events to an InputQueue for native code (i.e. there is baked-in special-case support for how to route input events to native apps that are using an InputQueue and the lifecycle of these events is tracked by these AsyncInputStage implementations that have an onFinishedInputEvent callback that will relay whether the native application really handled the event or not. (See: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewRootImpl.java and e.g. NativePreImeInputStage)

On the other hand GameActivity does not get input events via an InputQueue - it instead hooks into the Activity callbacks like onKeyPress which don't have the same kind of asynchronous tracking.

With the Activity callbacks they are

  1. run in the Java main thread and
  2. require the application to synchronously determine whether the event will be handled before returning

This means that we can't do anything with the InputStatus that applications return when processing input events with the GameActivity backend since there no way for us to trigger default handling for events if they have already been buffered.

To account for special cases like the volume keys then GameActivity instead supports the notion of an event filter that will run on the Java main thread and can decide before buffering events whether they should be buffered or ignored (so they get default handling) and by default GameActivity filters out volume key buttons, so native applications wouldn't see them and they would get default volume key handling.

The GameActivity approach makes the full Java api for KeyEvent and MotionEvent accessible, including for example getUnicodeChar which isn't exposed to the ndk and is more awkward to emulate for NativeActivity based applications.

In android-activity we don't currently expose this idea of an event filter and so applications get the default filter.

This means that GameActivity based apps don't see volume up/down keys (because if they weren't filtered out then there would be no way to trigger the default system behavior if they aren't handled by applications).

If we were to expose this idea of an event filter then we could also run the same filter on top of the InputQueue events for consistency.

For the motion events, I'm not aware of any reason why we couldn't change the default filter so that all motion events are buffered and exposed to applications but if any events might need default handling when applications aren't interested in them, there would be a similar issue as with the volume keys.