Android SDK
TapSdk auto-connects paired devices and can manage Text ↔ Controller across the activity lifecycle.
Package
| Source | TapWithUs/tap-android-sdk |
|---|---|
| License | Apache 2.0 |
| Status | Official · Maven 0.3.6 |
| Language | Java / Kotlin |
| Updated | September 2026 — V2 framed protocol (aligned with iOS + Python SDKs) |
Install
Require Maven Central, then:
implementation 'io.github.tapwithus:tap-android-sdk:0.3.6'Hello Tap
TapSdk sdk = TapSdkFactory.getDefault(this);
sdk.registerTapListener(new TapListener() {
@Override
public void onTapConnected(@NonNull String tapIdentifier) {
Log.d("Tap", "connected " + tapIdentifier);
}
@Override
public void onTapDisconnected(@NonNull String tapIdentifier) {
Log.d("Tap", "disconnected " + tapIdentifier);
}
@Override
public void onTapInputReceived(@NonNull String tapIdentifier, int data, int repeatData) {
boolean[] fingers = TapSdk.toFingers(data);
Log.d("Tap", "combination=" + data + " repeat=" + repeatData);
// fingers[0]=thumb … fingers[4]=pinky
// V2 taps always report repeatData == 1
}
@Override
public void onMouseInputReceived(@NonNull String tapIdentifier, @NonNull MousePacket data) { }
@Override
public void onAirMouseInputReceived(@NonNull String tapIdentifier, @NonNull AirMousePacket data) { }
@Override
public void onRawSensorInputReceived(@NonNull String tapIdentifier, @NonNull RawSensorData rsData) { }
@Override
public void onImuMotionInputReceived(@NonNull String tapIdentifier, @NonNull ImuMotionPacket packet) {
// packet.dx, packet.dy, packet.isMouse, packet.roll, packet.pitch, packet.yaw
}
@Override
public void onTapStandbyStateChanged(@NonNull String tapIdentifier, boolean standby) { }
@Override public void onBluetoothTurnedOn() { }
@Override public void onBluetoothTurnedOff() { }
@Override public void onTapStartConnecting(@NonNull String tapIdentifier) { }
@Override public void onTapResumed(@NonNull String tapIdentifier) { }
@Override public void onTapChanged(@NonNull String tapIdentifier) { }
@Override public void onTapShiftSwitchReceived(@NonNull String tapIdentifier, int data) { }
@Override public void onTapChangedState(@NonNull String tapIdentifier, int state) { }
@Override public void onError(@NonNull String tapIdentifier, int code, @NonNull String description) {
Log.e("Tap", description);
}
});
// Hook activity lifecycle so Text mode is restored in background:
@Override protected void onResume() { super.onResume(); sdk.resume(); }
@Override protected void onPause() { sdk.pause(); super.onPause(); }TapListener
Entry point: TapSdkFactory.getDefault(context) or construct TapSdk with TapBluetoothManager. Register with registerTapListener. Combination onTapInputReceived uses the same 1–31 bitmask as other platforms; repeatData is 1/2/3 for single/double/triple on classic devices. V2 taps always report repeatData == 1.
onTapShiftSwitchReceived carries Shift (0=off, 1=on, 2=locked) and Switch. Convert with TapSdk.toShiftAndSwitch. New V2 callbacks: onImuMotionInputReceived (dx/dy + Euler angles) and onTapStandbyStateChanged.
Modes
sdk.startControllerMode(tapIdentifier);
sdk.startTextMode(tapIdentifier);
sdk.startControllerWithMouseHIDMode(tapIdentifier);
sdk.startControllerWithFullHIDMode(tapIdentifier);
sdk.startRawSensorMode(tapIdentifier, (byte)0, (byte)0, (byte)0);
sdk.setDefaultMode(mode, true); // applyImmediateBy default the SDK switches to Controller on connect and back to Text when backgrounded (unless you disablePauseResumeHandling). On V2 devices these mode calls are translated into feature commands automatically.
Raw sensors
Finger accelerometers plus thumb IMU (Tap Strap 2). Sensitivities: device accelerometer 1–4, IMU gyro 1–4, IMU accelerometer 1–5; 0 means default. V2 devices stream the thumb IMU only — there are no finger accelerometers.
sdk.startRawSensorMode(tapIdentifier, (byte)0, (byte)0, (byte)0);
// in onRawSensorInputReceived:
if (rsData.dataType == RawSensorData.DataType.Device) {
Point3 thumb = rsData.getPoint(RawSensorData.iDEV_THUMB);
} else if (rsData.dataType == RawSensorData.DataType.IMU) {
Point3 gyro = rsData.getPoint(RawSensorData.iIMU_GYRO);
}TapXR
TapXR input state is separate from TAP input mode: force air-mouse, tapping, or leave it to the user. Spatial Control (extended pinch / drag / swipe aggregation) is for qualified TapXR developers — request access.
tapSdk.setDefaultXRState(TapXRState.tapping(), true);
tapSdk.setDefaultXRState(TapXRState.userControl(), false);
tapSdk.startXRAirMouseState("identifier");
tapSdk.startXRTappingState("identifier");
tapSdk.startXRUserControlState("identifier");Classic XR air-gesture constants on AirMousePacket include XR_AIR_GESTURE_NONE (100), XR_AIR_GESTURE_THUMB_INDEX (101), XR_AIR_GESTURE_THUMB_MIDDLE (102). These stream several times per second — take the majority of the last three events. V2 devices use UnifiedAirGesture instead (see below).
V2 devices
TapXR with V2 firmware speaks a framed BLE protocol instead of classic characteristic-per-stream. The SDK detects the protocol after connection — no code changes for basic usage. Check with sdk.isV2Tap(tapIdentifier). A keepalive is sent automatically every 10 seconds to every connected V2 Tap.
- Taps still arrive via
onTapInputReceived(V2repeatDatais always 1) - Mouse movement via
onMouseInputReceivedandonImuMotionInputReceived(includes roll / pitch / yaw) - Air gestures via
onAirMouseInputReceivedwithUnifiedAirGesturecodes 100–114 (direction, pinches AB/AC/AD/AE, fist, hold variants) - V2-only APIs on a v1 device report
TapSdk.ERR_V2_NOT_SUPPORTED(103) viaonError
@Override
public void onAirMouseInputReceived(@NonNull String tapIdentifier, @NonNull AirMousePacket data) {
if (sdk.isV2Tap(tapIdentifier)) {
UnifiedAirGesture gesture = UnifiedAirGesture.fromCode(data.gesture.getInt());
if (gesture != null) {
switch (gesture) {
case LEFT: case RIGHT: case UP: case DOWN:
case AB: // thumb-index pinch (AC/AD/AE for other fingers)
case FIST:
case AB_HOLD: // held pinch / drag
case NONE:
break;
}
}
} else {
// classic device — AirMousePacket.AIR_MOUSE_GESTURE_* / XR_AIR_GESTURE_*
}
}Independent feature toggles (DeviceFeature): RAW_IMU_DATA, MODEL_DETECTION, IMU_MOTION_DATA, STANDBY_GESTURE_DETECTION. XR state maps onto the vision sensor: startXRTappingState → TAPPING model + TRIGGER op-mode; startXRAirMouseState → AIR_GESTURE model + STREAM. Getters reply on a TapV2Callback (null after 2s).
boolean isV2 = sdk.isV2Tap(tapIdentifier);
sdk.setFeature(tapIdentifier, DeviceFeature.MODEL_DETECTION, true);
sdk.getFeature(tapIdentifier, DeviceFeature.MODEL_DETECTION, (identifier, enabled) -> {
// enabled is null on timeout
});
sdk.setVisionSensorModel(tapIdentifier, VisionSensorModel.AIR_GESTURE); // or TAPPING
sdk.setVisionSensorOpMode(tapIdentifier, VisionSensorOpMode.STREAM); // TRIGGER / STREAM_ON_TRIGGER
sdk.setImuSensitivity(tapIdentifier, /* gyro 0-5 */ 3, /* accelerometer 0-4 */ 2);
sdk.setStandbyState(tapIdentifier, true);
sdk.sendKeepAlive(tapIdentifier);Haptics
Up to 18 durations (ms) as haptic, pause, haptic, pause…. On V2, vibrate is sent through the framed protocol automatically.
sdk.vibrate(tapIdentifier, new int[] { 500, 100, 500 });