TAP

Android SDK

TapSdk auto-connects paired devices and can manage Text ↔ Controller across the activity lifecycle.

  1. Package
  2. Install
  3. Hello Tap
  4. TapListener
  5. Modes
  6. Raw sensors
  7. TapXR
  8. V2 devices
  9. Haptics

Package

SourceTapWithUs/tap-android-sdk
LicenseApache 2.0
StatusOfficial · Maven 0.3.6
LanguageJava / Kotlin
UpdatedSeptember 2026 — V2 framed protocol (aligned with iOS + Python SDKs)

Install

Require Maven Central, then:

build.gradle
implementation 'io.github.tapwithus:tap-android-sdk:0.3.6'

Hello Tap

MainActivity.java
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(); }
NOTEPair Tap in Android Bluetooth settings first. The SDK connects to paired devices; it does not discover unpaired Taps.

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

java
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); // applyImmediate

By 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.

java
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.

java
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 (V2 repeatData is always 1)
  • Mouse movement via onMouseInputReceived and onImuMotionInputReceived (includes roll / pitch / yaw)
  • Air gestures via onAirMouseInputReceived with UnifiedAirGesture codes 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) via onError
UnifiedAirGesture
@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).

V2 APIs
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.

java
sdk.vibrate(tapIdentifier, new int[] { 500, 100, 500 });