# Android Integration

> **Library 2.0.0.213** · SAE J2534 v04.04 API

On Android the library ships as `libj2534.so` (JNI) for four ABIs together
with a ready-made Kotlin wrapper. A complete working application is provided
in the **j2534_android_demo** project — the fastest way to start is to copy
the wrapper package from it unchanged.

## What to copy into your app

```text
app/src/main/jniLibs/
├── arm64-v8a/    libj2534.so + libc++_shared.so
├── armeabi-v7a/  libj2534.so + libc++_shared.so
├── x86/          libj2534.so + libc++_shared.so
└── x86_64/       libj2534.so + libc++_shared.so

app/src/main/java/com/quantexlab/
├── j2534/
│   ├── J2534JNI.kt   — API entry point (external functions)
│   ├── Core.kt       — message & result classes
│   ├── Consts.kt     — protocol/IOCTL/flag constants
│   └── utils.kt      — SET_CONFIG list helpers
└── device/
    └── BLEManager.kt — BLE transport backend (used by native code)
```

> **Keep the package names, class names and field names exactly as provided.**
> The native library resolves these classes, methods and fields by name at
> runtime (JNI reflection). Renaming `com.quantexlab.j2534.ResConnect.chnlId`,
> moving `BLEManager` to another package, or trimming "unused" members will
> break the library at runtime, typically as a `null` return from the API.

## API surface

`J2534JNI` mirrors the C API. Construct it with a `Context` (this wires up the
BLE backend), then call:

```kotlin
val j2534 = J2534JNI(context)

// discovery (linkType 1 = BLE), returns ready-to-use connection strings
val devices: Array<String?> = j2534.ptScanForDevices(1, 5000)
// e.g. "N49999;m12:34:56:78:9A:BC"

val open = j2534.ptOpen("N49999;m12:34:56:78:9A:BC")
if (open.status == STATUS_NOERROR) {
    val conn = j2534.ptConnect(open.devId, ISO15765, 0, 500000)
    // filters / read / write / ioctl — see the demo's MainViewModel.kt
    j2534.ptDisconnect(conn.chnlId)
    j2534.ptClose(open.devId)
}
```

Available methods: `ptOpen`, `ptClose`, `ptConnect`, `ptDisconnect`,
`ptReadMsgs`, `ptWriteMsgs`, `ptStartPeriodicMsg`, `ptStopPeriodicMsg`,
`ptStartMsgFilter`, `ptStopMsgFilter`, `ptSetProgrammingVoltage`,
`ptReadVersion`, `ptGetLastError`, `ptIoctl`, `ptScanForDevices`,
`ptOtaUpdate`, `ptOtaAbort`, `ptSetLogLevel`.

Each call returns a result object (`ResOpen`, `ResConnect`, `ResReadMsgs`, …)
carrying `status` (see [ERROR_HANDLING.md](ERROR_HANDLING.md)) plus the output
values.

### Firmware update (OTA)

```java
public native ResOtaUpdate ptOtaUpdate(int devId, String firmwarePath,
                                       OtaProgressListener callback);
public native ResOtaAbort ptOtaAbort(int devId);

public interface OtaProgressListener {
    void onProgress(int current, int total); // блоки, 1-based
}

public class ResOtaUpdate { public int status; }
public class ResOtaAbort  { public int status; }
```

`ptOtaUpdate` blocks until the update finishes; call it off the main thread.
`firmwarePath` is a filesystem path readable by the app (copy the
`update.bin` into app storage first). `status` is `0` on success or a
negative `ota_result_t` (see `j2534_ota.h` and
[FIRMWARE_UPDATE.md](FIRMWARE_UPDATE.md)). On success the adapter reboots
and `devId` becomes invalid — reconnect with a new `ptOpen` after the
adapter comes back (BLE: allow ~10 s). `ptOtaAbort` cancels an in-progress
update; the adapter returns to normal operation without rebooting.

## ptIoctl support

The JNI layer marshals a fixed set of IOCTLs:

| IOCTL | numOf | pInput | result in `ResIoctl.output` |
|-------|-------|--------|------------------------------|
| `GET_CONFIG` / `SET_CONFIG` | number of parameters | packed `PtConfig` list (`configListToByteArray`) | `GET_CONFIG`: packed list (`byteArrayToConfigList`) |
| `READ_VBATT` | 0 | null | `uint32` mV, little-endian at `[0..3]` |
| `FIVE_BAUD_INIT` | 0 | address byte | key bytes |
| `FAST_INIT` | 0 | wake-up bytes | response bytes |
| `CLEAR_TX_BUFFER`, `CLEAR_RX_BUFFER`, `CLEAR_PERIODIC_MSGS`, `CLEAR_MSG_FILTERS`, `CLEAR_FUNCT_MSG_LOOKUP_TABLE` | 0 | null | — |
| `REQUEST_CONNECTION` (TP 2.0) | byte count | address bytes | — |
| `ISO13400_DISCOVER_VEHICLES`, `ISO13400_CONNECT_TCP` | 0 | null | — |
| `ISO13400_ACTIVATE_ROUTING` | 0 | null | response code, LE `uint32` at `[0..3]` |
| `ISO13400_GET_VEHICLE_INFO` | 0 | null | `[0..3]` = data length LE, then 40-byte entries |

Other IOCTL IDs return `ERR_IOCTL_ID_NOT_SUPPORTED`. For the DoIP flow see
[DOIP.md](DOIP.md) and the demo's `connectVehicleDoIPNative()`.

## Logging

The library logs in two places: logcat (tag per module, e.g. `pt.so`) and a
`.qlog` file per `PassThruOpen` in

```text
<getExternalFilesDir(null)>/sdlogs/id<N>_YYYYMMDD_HHMMSS.qlog
= /sdcard/Android/data/<applicationId>/files/sdlogs/
```

The directory is resolved through the JNI context, so it only exists once
`J2534JNI(context)` (i.e. `setAndroidBleContext`) has run. `.qlog` files older
than 30 days are deleted on every `PassThruClose`.

Verbosity — and whether the file is written at all — is the log level:
`-1` off, `0` errors, `1` +warnings, `2` +info, `3` +debug (default),
`4` +verbose. At `-1` no file is created and no message payload is written
anywhere.

```java
public native void ptSetLogLevel(int level);
```

```kotlin
external fun ptSetLogLevel(level: Int)   // in J2534JNI

j2534.ptSetLogLevel(-1)   // release build: logging off
j2534.ptSetLogLevel(3)    // support case: full log
```

Call it before `ptOpen`. The level set this way wins over the `log_level` key
of `j2534.json` ([CONFIGURATION.md](CONFIGURATION.md)) — a config file left in
app storage cannot re-enable logging in a release build. Without the call the
`j2534.json` value applies, and until `setAndroidBleContext` has run that file
cannot be read either, so the built-in default (up to debug) is in effect.

## BLEManager contract

`BLEManager` is the BLE transport backend. The native library calls it through
JNI — it is **not** an application-facing API:

- `J2534JNI` must keep a field `bleManager: BLEManager` (the native code looks
  this field up by name).
- Native code invokes `scanForBleDevices(timeout)`, `bleOpen(address, deviceId)`,
  `bleClose()`, `write(value)` and delivers data back through the native
  callbacks `onConnect`/`onRead`/`onWrite` declared in the class.

Do not call `BLEManager` directly and do not modify it — use the `J2534JNI`
methods instead.

## Permissions

```xml
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
```

`BLUETOOTH_CONNECT`/`BLUETOOTH_SCAN` need runtime grants on Android 12+;
location permission is required for BLE scanning on older versions.

Wi-Fi connections (`;n<ip>`): the phone must be joined to the ScanDoc's Wi-Fi
network; on some phones mobile data must be disabled to avoid routing issues.

## Requirements

- minSdk 24 (Android 7.0)
- The demo project builds with AGP 8.x, which requires **JDK 17+** (Android
  Studio's bundled JDK works out of the box).

## Demo application

The `j2534_android_demo` project demonstrates: BLE device scanning and
connection, ISO 15765 sessions (11-bit and 29-bit), reading battery voltage,
VIN and OBD-II data, and both DoIP variants (native ISO 13400 and raw TCP
socket). Start from `MainViewModel.kt` — every API call pattern lives there.
