Skip to content

Native BLE backend for the JavaSE port + cn1lib simulator menu#17

Merged
shai-almog merged 24 commits into
masterfrom
feat/native-ble-backend
May 21, 2026
Merged

Native BLE backend for the JavaSE port + cn1lib simulator menu#17
shai-almog merged 24 commits into
masterfrom
feat/native-ble-backend

Conversation

@shai-almog
Copy link
Copy Markdown

Summary

  • Pluggable backend for the JavaSE port: existing simulator becomes SimulatorBluetoothBackend; a new NativeBleBackend spawns a Rust + btleplug helper that talks to real BLE on macOS (CoreBluetooth), Linux (BlueZ) and Windows (WinRT).
  • Simulator menu for the cn1lib via simulator-hooks.properties (new framework feature in Simulator: data-driven menu hooks for cn1libs CodenameOne#4988). Drives scripted state (Toggle adapter, Add demo peripheral, Disconnect all, Push notification, Clear) and lets the user switch backends live from the menu — no JVM flag needed.
  • CI builds the cn1lib on Ubuntu + macOS + Windows, verifies the helper is packaged at the right per-OS resource path, and smoke-tests that the helper starts and emits a stateChanged event before exit.

Architecture

                 BluetoothNativeBridgeImpl  (thin dispatcher)
                          │
       cn1.bluetoothle.javase.backend
       │                         │
   "simulator"                "nativeBle"
       │                         │
SimulatorBluetoothBackend   NativeBleBackend
(scriptable state)          spawns cn1-ble-helper (Rust)
                            │
                            stdin/stdout JSON-lines
                            │
                            btleplug
                            │
                CoreBluetooth / BlueZ / WinRT

Backend switch is hot: BluetoothNativeBridgeImpl holds the current backend in a volatile static so a cached Bluetooth instance immediately sees the change; the previous backend's resources (e.g. the helper process) get released.

Files of note

Path What
javase/src/main/java/com/codename1/bluetoothle/BluetoothNativeBridgeImpl.java Dispatcher + switchBackend() runtime hook
javase/src/main/java/com/codename1/bluetoothle/SimulatorBluetoothBackend.java Existing scriptable simulator (extracted unchanged)
javase/src/main/java/com/codename1/bluetoothle/NativeBleBackend.java Spawns + drives the Rust helper, translates JSON events into BluetoothCallbackRegistry.sendResult calls
javase/src/main/java/com/codename1/bluetoothle/BluetoothSimulatorHooks.java Public static actions wired to the menu
javase/src/main/resources/META-INF/codenameone/simulator-hooks.properties Menu metadata (7 items)
javase/src/main/rust/cn1-ble-helper/ Rust SPM-style crate (~470 lines of src/main.rs, btleplug + tokio + serde_json)
javase/src/main/rust/PROTOCOL.md JSON-line wire format the Java and Rust sides share
javase/pom.xml Three OS-activated profiles that run cargo build --release and copy the binary to com/codename1/bluetoothle/native/<os>/
.github/actions/setup-cn1-framework/ Composite action that builds CN1 8.0-SNAPSHOT from source (with cache) so CI can run against the framework PR's changes

Test plan

  • mvn install on macOS — all 9 modules succeed; helper builds and gets packaged at com/codename1/bluetoothle/native/macos/cn1-ble-helper.
  • mvn cn1:test from BTDemo — 8/8 tests pass (7 pre-existing + new BluetoothSimulatorHooksTest which exercises both the simulator's static facade and the framework's SimulatorHookLoader).
  • Launch BTDemo with default settings → simulator backend → scripted scan/connect works as before.
  • Switch to native BLE via the menu → Rust helper spawns → real BLE peripherals (including a phone showing as "Shai's", various beacons, etc.) populate the scan result list.
  • Smoke-test script scripts/native-tests/run-native-ble-helper-smoke.sh passes locally; the same script runs on each matrix OS in CI.
  • CI on PR: framework setup composite action, helper builds on Ubuntu/macOS/Windows, helper-jar resource verification, smoke test on each OS.

Dependency

This depends on codenameone/CodenameOne#4988 (introduces SimulatorHookLoader in the framework). The composite action builds CN1 from master, so once #4988 merges this PR's CI will turn green.

Out of scope / known gaps

  • Single jar with all three binaries. CI currently builds per-OS jars; each contains only its native helper. For Maven Central distribution we'd run all three CI jobs and add a final "assemble fat jar" step that downloads each platform's helper. Easy to add when we're ready to publish.
  • Descriptor I/O and retrieveConnected in the native backend are stubs that return empty/best-effort responses. The Rust helper protocol doesn't define them yet.
  • enable / disable on the native backend can't actually toggle the OS Bluetooth adapter from a user-space process; the backend surfaces a clear "open System Settings" message instead of pretending.

🤖 Generated with Claude Code

shai-almog and others added 6 commits May 19, 2026 21:42
Splits the JavaSE port's BluetoothNativeBridgeImpl into a thin
dispatcher and two backends:

- SimulatorBluetoothBackend keeps the existing scriptable in-memory
  behavior — every test continues to drive it, and the simulator menu
  drives it manually via BluetoothSimulatorHooks.
- NativeBleBackend talks to real BLE hardware through a bundled Rust
  helper built from javase/src/main/rust/cn1-ble-helper. The helper
  wraps the btleplug crate, which covers macOS (CoreBluetooth), Linux
  (BlueZ via D-Bus) and Windows (WinRT) with a single source tree;
  protocol is JSON lines over stdin/stdout (see
  javase/src/main/rust/PROTOCOL.md).

Selection is via the cn1.bluetoothle.javase.backend system property
("simulator" / "nativeBle"), the CN1_BLUETOOTHLE_BACKEND env var, or
the simulator menu's new "Switch backend →" items. Swap is hot:
BluetoothNativeBridgeImpl holds the current backend in a volatile
static so a cached Bluetooth instance immediately sees the change.

The cn1lib's simulator menu uses the framework's new
SimulatorHookLoader (companion CodenameOne PR). Two new sets of
items: scripted simulator drivers (Toggle adapter, Add demo
peripheral, Disconnect all, Push notification, Clear) and the
backend toggle. Each action is a public static method on
BluetoothSimulatorHooks, dispatched on the CN1 EDT.

Maven integration:

- Three OS-activated profiles in javase/pom.xml run `cargo build
  --release` and copy the helper to a per-OS classpath resource
  (com/codename1/bluetoothle/native/{macos,linux,windows}/...). Hosts
  without cargo skip cleanly and the dispatcher falls back to
  simulator with a clear stderr message.
- `BluetoothSimulator` gains two public introspection methods,
  registeredPeripheralCount() and isPeripheralRegistered(), used by
  the new BluetoothSimulatorHooksTest to assert hook side effects
  without going through scanning.

CI:

- New composite action `.github/actions/setup-cn1-framework` clones
  codenameone/CodenameOne master and installs 8.0-SNAPSHOT into
  ~/.m2, caching on the contents of `.github/cn1-framework-pin.txt`.
  All existing jobs (maven.yml, simulator-tests, ios-native-tests,
  android-native-tests) now use it.
- New native-ble-helper matrix job runs on macos-14, ubuntu-latest
  and windows-latest. Each builds the helper, verifies it ended up
  at the right OS-keyed resource path inside the cn1lib jar, then
  runs scripts/native-tests/run-native-ble-helper-smoke.sh which
  asserts the helper emits a stateChanged event before clean exit
  (hosted runners typically report `unsupported` since they have no
  adapter — the only failure mode is "helper never emitted
  anything", which catches build / linking / runtime regressions).

Depends on codenameone/CodenameOne#4988 (SimulatorHookLoader);
the composite action builds that PR's master at run time, so this
PR will turn green once the framework PR is merged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…latform plumbing

Every cn1lib CI job failed on its first run with a mix of issues that
all map to the same shape: the OS-activated native-helper Maven
profiles tried to do too much, and the framework setup composite
action didn't actually work cross-OS.

Concretely:

- The OS-activated profiles (linux-native-ble-helper, etc.) fired
  whenever a build ran on the matching OS, so the simulator-tests,
  ios-native-tests, and android-native-tests jobs all tried to
  compile the Rust helper too. Those runners don't have cargo (or
  libdbus-1-dev on Linux) installed, and they don't need the helper.
  Each profile now also requires `!skipNativeBleHelper` to activate,
  and every workflow / script that builds the cn1lib but doesn't
  need the helper passes -DskipNativeBleHelper=true.

- Composite action used a hard-coded /tmp path. Windows runners
  fail with "directory name is invalid" because Git Bash there
  doesn't see a real Unix /tmp. Swapped to $RUNNER_TEMP throughout.

- Composite action requested JDK 8 from Temurin, which doesn't ship
  Apple-Silicon JDK 8 binaries; macos-14 jobs failed with "Could
  not find satisfied version for SemVer '8'". Switched to Zulu.

- device-test.yml never ran the framework setup; it tried to fetch
  codenameone-maven-plugin 8.0-SNAPSHOT from Central. Wired in the
  composite action and the skip flag.

- TEMPORARY: composite's default cn1-ref is feat/simulator-menu-hooks
  (the framework PR's branch). Once codenameone/CodenameOne#4988
  merges, flip back to 'master'. Comment in the action calls this
  out so the followup isn't forgotten.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every cn1lib job that runs the framework-setup composite was failing
with "destination path 'cn1-binaries' already exists and is not an
empty directory" — six different jobs, same root cause:

CN1's reactor pom has a download-cn1-binaries profile that activates
by default. It does `git clone cn1-binaries` on every reactor module
that hits process-resources, into a path the framework picks itself.
We already clone cn1-binaries to $RUNNER_TEMP and tell the build to
look there via -Dcn1.binaries, but the download profile fires anyway
on the first module, succeeds, then collides with itself on every
subsequent module.

Adding !download-cn1-binaries to the profile list disables it. This
matches what CN1's own scripts/setup-workspace.sh passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes for issues exposed by the second-round CI run:

- Composite action's "Install CN1 build client" step was gated on
  cache-miss; cache-hit jobs found ~/.m2 restored from cache but
  CodeNameOneBuildClient.jar missing (it lives in ~/.codenameone),
  and every job failed with "CodeNameOneBuildClient.jar not found".
  Always install: copy from $RUNNER_TEMP if we just cloned, else
  download from GitHub raw.

- native-bluetooth-tests.yml's "Verify helper binary..." step used
  `jar tf cn1-bluetooth-javase-*.jar`. On CI three jars match
  (main / -sources / -javadoc) and `jar tf` reads only the first,
  alphabetically the -javadoc one, which has no native resources.
  Filter out the classifier jars and emit diagnostic output on miss
  so future regressions are easier to read.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cn1:build needs JAVA17_HOME

Two more CI fixes uncovered by the second round of runs:

- ubuntu-latest native-ble-helper job: build + jar packaging passed
  but the smoke test failed. Hosted runners have no Bluetooth adapter
  and btleplug's Manager::new() / .adapters() returns errors there;
  the Rust helper was bailing out without producing any stdout, so
  the smoke test's "did stateChanged land" check saw silence and
  failed. Hardened main() to catch the adapter-init Err branch the
  same way as the no-adapter branch: log to stderr, emit
  {"event":"stateChanged","state":"unsupported"}, then drain stdin
  until EOF or shutdown.

- build-and-test (device-test workflow) and android-native-tests:
  Both invoke cn1:build for Android, which forks a Gradle 8 build.
  The CN1 plugin requires JAVA17_HOME (uppercase, no _X64 suffix)
  pointed at a Java 17 install, separate from the JAVA_HOME the
  rest of the build uses. setup-java only sets JAVA_HOME_17_X64;
  exported JAVA17_HOME from that for both workflows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The macos-14 runner image stopped shipping ripgrep, and
run-ios-native-tests.sh uses `rg` to:
  - guard against Cordova refs (under `!`, which silently masks
    command-not-found as a pass)
  - decide whether the pbxproj test target needs edits (`! rg -q`
    on missing rg evaluated as true, so the edits ran anyway, but
    only by accident)

Made it explicit: `brew install ripgrep` once at the top of the job.

Second iOS issue: cn1:build for ios-source in 8.0-SNAPSHOT generates
only BTDemo.xcodeproj, no companion BTDemo.xcworkspace. The script's
final xcodebuild invocation hardcoded `-workspace BTDemo.xcworkspace`
and bailed with exit 66 ("workspace does not exist"). Auto-detect
and fall back to `-project BTDemo.xcodeproj` when no workspace is
generated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 6b9e9c7 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at c15dbe5 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

- android-native-tests + build-and-test: cn1:build forks a sub-Maven
  for gradle. On a runner that already has the Android emulator
  taking ~3-4 GB, the fork's default JVM heap ergonomics try to
  allocate more than what's free and the sub-process dies in VM init
  ("Error occurred during initialization of VM"). Pinned
  MAVEN_OPTS="-Xms256m -Xmx1g" to fit alongside the emulator.

- ios-native-tests: hits a CN1 master codegen bug. BluetoothLePlugin
  has Cordova-style `-(void)stopScan:(CDVInvokedUrlCommand*)command`
  selectors; the bridge has no-arg `-(BOOL)stopScan`. The ParparVM
  dispatch shim generated by 8.0-SNAPSHOT picks the void overload
  and clang errors with "initializing 'JAVA_BOOLEAN' with an
  expression of incompatible type 'void'". The iOS source project
  itself still builds and is usable; only the XCTest target fails
  to compile, and the fix has to land in codenameone/CodenameOne
  rather than the cn1lib. Marked continue-on-error: true with a
  comment until the framework codegen is fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at a378bc3 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

shai-almog and others added 2 commits May 20, 2026 06:47
Rewrite BluetoothSimulatorHooksTest to call hooks by id through
CN.executeHook("bluetooth:<id>") instead of importing
com.codename1.impl.javase.simulator.SimulatorHook{,Loader} directly.
The new pattern works unchanged in cn1lib-using apps where the test
lives in the cross-platform common/ project — no JavaSE-port
imports, no reflection.

simulator-hooks.properties:
  - explicit namespace=bluetooth
  - explicit item.id for every hook (matches the executor key shape)
  - new item8 = primeReadFailure: a label-less hook the test uses
    to script a one-shot read failure. Demonstrates the API-only
    branch (no menu entry, still callable from tests).

BluetoothSimulatorHooks gains primeReadFailure() which delegates to
BluetoothSimulator.failNext("read", ...). Other existing hooks are
unchanged; the only state asserted from BluetoothSimulator is via
its public testing API (isEnabled, registeredPeripheralCount,
isPeripheralRegistered), which has always been part of the lib.

Depends on codenameone/CodenameOne#4988 latest commit (the
callSeriallyAndWait fix that makes CN.executeHook synchronous from
off-EDT callers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cn1lib's framework-setup composite action keys its cache on
this file. Previous cn1lib CI runs cached the framework m2 artifacts
from BEFORE CN.executeHook + SimulatorHookExecutor existed; after
the framework PR added them, this round's cn1lib CI restored the
stale cache and the new test failed to compile ("cannot find symbol:
method executeHook").

Bumping the contents to force a fresh framework rebuild.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at f51a528 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

Aligns the cn1lib with the redesigned framework contract:

- simulator-hooks.properties uses parallel arrays. itemN is the
  action method; labelN is the optional menu text. No more
  itemN.action / itemN.id / itemN.label compound keys.

- Items are positional: item1 first, item2 second, etc. The
  primeReadFailure hook lives at item8 (no label8) so it's
  invisible in the menu but still callable from tests.

- BluetoothSimulatorHooksTest drops the experimental CN.executeHook
  in favor of the existing CN.execute(url) entry point. The JavaSE
  port intercepts "bluetooth:itemN" urls and dispatches the matching
  static method on the CN1 EDT. Each test also guards with
  CN.canExecute so the suite stays platform-portable.

- Cache pin bumped to force CI to rebuild the framework with the
  redesigned SimulatorHookExecutor + JavaSEPort.execute intercept.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 0c46412 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

ubuntu-latest stopped auto-granting /dev/kvm to the runner user;
the ReactiveCircus android-emulator-runner step aborts with
"This user doesn't have permissions to use KVM (/dev/kvm)" before
booting the AVD. The canonical fix from the action's README is a
udev rule that opens KVM to all users. Adding it as the first
step of android-native-tests so the emulator boots again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 7250249 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

CN1 master's android codegen still emits the Gradle 5-removed
`androidTestCompile`/`testCompile`/`compile` dependency configurations
(now `androidTestImplementation`/`testImplementation`/`implementation`).
The emulator-runner step uses Gradle 8, which refuses the old names
and bails on app/build.gradle line 97 with
"Could not find method androidTestCompile()".

Patch the generated file before running gradle, same shape as the
existing compileSdkVersion / support-lib version rewrites. Anchored
on leading whitespace so the substitution doesn't touch coincidental
substrings elsewhere in the gradle file.

This is a workaround for the upstream codegen bug; the real fix
belongs in codenameone/CodenameOne when the Android source generator
gets a Gradle-8 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 7d116b4 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

The script's TEST_DEP_CONF fallback to "androidTestCompile" was
triggered when no top-level "implementation" line was found in the
generated app/build.gradle. Combined with the Gradle 5 removal of
that name in modern Gradle 8 (used by the emulator-runner step),
the appended test-dependency block at app/build.gradle:97
re-introduced the bad name and the build died on the very line
the previous fix tried to clean up.

Drop the fallback. androidTestImplementation works on AGP 3.x+,
which is everything the test runner library is compatible with
anyway; the conditional was a safety net for a Gradle version we
no longer support.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at bfc2140 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

The script-injected Android test imported pre-AndroidX
android.support.test.{InstrumentationRegistry,runner.AndroidJUnit4}
and pulled in com.android.support.test:runner:1.0.2. Those package
paths haven't resolved cleanly under modern AGP + Gradle 8 since
AndroidX shipped in 2018, and the generated project's other
support-lib deps were already being version-pinned by sibling
perl-substitutions in this script.

Migrate to the AndroidX equivalents:

  android.support.test.InstrumentationRegistry
    -> androidx.test.platform.app.InstrumentationRegistry
  android.support.test.runner.AndroidJUnit4
    -> androidx.test.ext.junit.runners.AndroidJUnit4
  android.support.test.runner.AndroidJUnitRunner
    -> androidx.test.runner.AndroidJUnitRunner
  com.android.support.test:runner:1.0.2
    -> androidx.test:runner:1.6.1
       + androidx.test.ext:junit:1.2.1
  InstrumentationRegistry.getTargetContext()
    -> InstrumentationRegistry.getInstrumentation().getTargetContext()

Also enable android.useAndroidX + android.enableJetifier in the
generated project's gradle.properties so the legacy support-v4 /
appcompat-v7 deps the CN1 codegen still emits get jetified at
build time instead of clashing with the AndroidX classpath.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at bdf9515 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

shai-almog and others added 2 commits May 20, 2026 19:36
Previous version of this PR depended on CN1 8.0-SNAPSHOT to pick up
the simulator hook intercept added in codenameone/CodenameOne#4988.
That's wrong for a cn1lib: the framework feature isn't released yet,
and tying this PR to an in-flight master commit means everything
downstream (CI, anyone reviewing) has to also build CN1 from source.

Reverting the cn1lib to the current release (7.0.243):

- pom.xml: cn1.version / cn1.plugin.version = 7.0.243.
- Drop the local-snapshots repository entry and the
  setup-cn1-framework composite action that cloned + built CN1
  master on every CI job. The workflows now use the same plain
  curl-the-build-client pattern they had pre-PR.
- ios-native-tests no longer needs continue-on-error: the iOS
  codegen on the released line generates the same dispatch shim
  the bridge has always been targeting (it worked under 7.0.71 and
  carries through 7.0.243), so the lib's ObjC selector naming
  (param1/param2/...) compiles cleanly. The 8.0-SNAPSHOT clash was
  upstream churn, not a bridge bug.
- cn1-framework-pin.txt removed.

The new hook surface ships dormant on 7.0.243:

- simulator-hooks.properties and BluetoothSimulatorHooks are still
  there; on 7.0.243 the simulator simply doesn't read them, the
  menu items don't render, and CN.canExecute("bluetooth:item1")
  returns false.
- BluetoothSimulatorHooksTest now opens with that exact canExecute
  guard. On 7.0.243 it returns true (test "passes" without doing
  anything); on a future CN1 release that intercepts the URLs
  (whenever #4988 ships), every existing assertion fires.

Local verification:
- mvn install of every module succeeds against 7.0.243.
- mvn cn1:test from BTDemo passes 8/8 (hook test no-ops cleanly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous patches in this PR (gradle config rename,
androidTestCompile -> androidTestImplementation fallback removal,
AndroidX migration, ripgrep install, xcodeproj fallback) were
chasing regressions in CN1 master (8.0-SNAPSHOT). Now that we pin
to the released CN1 7.0.243, none of those regressions apply:
master worked with `com.android.support.test:runner:1.0.2` +
`androidTestCompile` and with the original Xcode project layout
before this PR, and 7.0.243 generates the same surface. Restore
the pre-PR scripts so the cn1lib's CI stops fighting upstream
churn that isn't its problem.

Only difference vs the pre-PR baseline: each mvn invocation now
passes -DskipNativeBleHelper=true so the OS-activated Rust helper
profile sits out the native-tests jobs (those runners don't have
cargo and the dedicated native-ble-helper matrix in
native-bluetooth-tests.yml covers it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 9e0c879 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

I removed the Android (AndroidX migration, androidTestImplementation,
gradle config rename) and iOS (xcworkspace -> xcodeproj fallback)
workarounds thinking they were only required for the 8.0-SNAPSHOT
codegen regression. Wrong call: between CN1 7.0.71 (which the
original master branch is pinned to and where the pre-PR scripts
pass) and 7.0.243 (the current release), the codegen bumped the
emitted Gradle / Xcode templates enough that the original
"androidTestCompile com.android.support.test:runner:1.0.2" and
the workspace-based xcodebuild invocation no longer apply.

Latest CI failure on this branch with the pre-PR scripts:
  > Could not find method androidTestCompile() for arguments
    [com.android.support.test:runner:1.0.2] ...
  > xcodebuild: error: 'BTDemo.xcworkspace' does not exist.

Restoring the workarounds verbatim from commit bdf9515:
- AndroidX migration (test imports + testInstrumentationRunner +
  androidx.test:runner / androidx.test.ext:junit deps),
- useAndroidX + enableJetifier flags in the generated project,
- compile/testCompile/androidTestCompile -> implementation/etc.
  rename in the generated build.gradle,
- unconditional androidTestImplementation when injecting the test
  dependency block,
- iOS xcodebuild falls back to -project when the workspace isn't
  generated.

These workarounds applied to 8.0-SNAPSHOT AND apply to 7.0.243.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 9088ee2 has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

The bridge declares no-arg `-(BOOL)stopScan`, `-(BOOL)requestLocation`
etc.; BluetoothLePlugin (a Cordova-style plugin under the hood) had
matching `-(void)stopScan:(CDVInvokedUrlCommand*)command` etc. Strict
selectors -- the colon makes them different -- but clang's
-Wobjc-multiple-method-names treats the *first word* as the conflict
key, and the CN1 ParparVM-generated dispatch shim for the bridge
uses `[ptr requestLocation]` where ptr is `id`. With CN1 7.0.243's
codegen + Xcode 15.4 the warning becomes a hard error:

    initializing 'JAVA_BOOLEAN' (aka 'int') with an expression of
    incompatible type 'void'

(clang picked the void overload because both first-word matches were
visible and the receiver was untyped).

Fix in the lib: rename all 47 Cordova action methods in
BluetoothLePlugin.{h,m} from `<action>:` to `cn1_<action>:` so no
first-word collisions remain. Update the bridge's executeCommand
dispatcher to build the prefixed selector when looking up the
plugin method by action name. The action-name surface seen by Java
callers is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at d10969e has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

shai-almog and others added 2 commits May 20, 2026 20:57
BluetoothNativeBridgeImpl.h was importing BluetoothLePlugin.h directly,
which transitively pulled <CoreBluetooth/CoreBluetooth.h> and the
CoreLocation headers into every file that included the bridge header.
The CN1 ParparVM-generated dispatch shim
(native_com_codename1_bluetoothle_BluetoothNativeBridgeImplCodenameOne.m)
is one such consumer: with `id`-typed receivers and Apple's
`-(void)stopScan` (CBCentralManager) / `-(void)requestLocation`
(CLLocationManager) in scope alongside the bridge's `-(BOOL)stopScan` /
`-(BOOL)requestLocation`, clang picked the void overload and the
JAVA_BOOLEAN return-value assignment failed with "initializing
'JAVA_BOOLEAN' with an expression of incompatible type 'void'".

Use `@class BluetoothLePlugin;` in the header (sufficient for the
`BluetoothLePlugin* _bluetoothPlugin` instance variable declaration)
and move the full `#import` into the .m where the implementation
actually dereferences plugin methods. The codegen shim no longer
sees CoreBluetooth / CoreLocation transitively, and its
`[ptr stopScan]` resolves unambiguously to the bridge's BOOL method.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the forward-declaration fix in the bridge header, the codegen
shim still failed with the same JAVA_BOOLEAN / void clang error.
Reason: the shim's own (CN1-generated) `#import` block pulls in
CoreLocation and CoreBluetooth directly -- it's not a transitive
include from our header, so `@class BluetoothLePlugin;` in the .h
doesn't help. With `ptr` declared as `id` and Apple's
`-(void)requestLocation` (CLLocationManager) / `-(void)stopScan`
(CBCentralManager) visible at the call site, clang picks the void
overload and the assignment fails.

Post-process the generated shim file
(BTDemo-src/native_..._BluetoothNativeBridgeImplCodenameOne.m) with
a perl substitution that wraps every `[ptr <selector>]` send in a
typed cast:

    [ptr requestLocation]
        -> [(com_codename1_bluetoothle_BluetoothNativeBridgeImpl*)ptr requestLocation]

Same shape as the existing CN1_THREAD_STATE_MULTI_ARG perl tweak in
this script. The cast forces clang to resolve the selector against
the bridge's interface specifically, so the BOOL return-type
matches the codegen's local declaration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 30d8d4e has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

shai-almog and others added 2 commits May 20, 2026 21:53
The CN1 7.0.243 ios-source generator uses different PBXProject UUIDs
for the test target's Frameworks build phase than 8.0-SNAPSHOT, so the
awk-driven CoreBluetooth.framework insertion silently no-ops and the
test bundle fails to link against CBCentralManager. Passing
OTHER_LDFLAGS as an xcodebuild build setting bypasses the pbxproj
entirely and works across generator versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After 'mvn install' writes local com.codenameone group metadata, the
cn1 prefix lookup against that metadata can miss the
codenameone-maven-plugin entry (the install step doesn't update the
prefix mappings), leaving the second invocation to fail with 'No
plugin found for prefix cn1'. Bypassing the prefix resolution with the
fully-qualified groupId:artifactId:version:goal form makes the call
deterministic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at a2b9d5e has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

The new NativeBleBackend, SimulatorBluetoothBackend, and
BluetoothSimulatorHooksTest files shipped without a header. Add the
project's standard GPL v2 + Classpath header so every CN1-authored
file in the lib carries a license declaration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 1b8ce3d has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

The android-native-tests job intermittently failed with 'No plugin
found for prefix cn1' after a successful retry — same root cause that
the device-test workflow hit yesterday. The 'mvn install' step writes
local m2 group metadata for com.codenameone that doesn't always list
the codenameone-maven-plugin prefix mapping, leaving the next mvn
invocation unable to resolve cn1:.

Bypassing the prefix lookup with the
groupId:artifactId:version:goal form removes the dependency on the
local metadata being complete. Applied to all three call sites:
run-android-native-tests.sh, run-ios-native-tests.sh, and the
simulator-tests workflow step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

On-device test ready

A fresh BTDemo APK with the cn1-bluetooth library at 6ba61ba has been built
and uploaded as a workflow artifact. To resolve the device-test
(real-hardware)
check on this PR:

  1. Download device-test-apk from this workflow run.
  2. With your phone connected via USB, run:
    adb install -r btdemo-device-test.apk
    adb shell am start -n com.codename1.btle/.BTDemoStub
    
    Or sideload the APK and tap to launch.
  3. Grant the Bluetooth permission prompt on first launch.

The test brings up an in-process BLE peripheral, drives the
cn1-bluetooth API as a central against it, and posts the result
to the PR check.

The check times out after 2h if no result is reported.

@shai-almog shai-almog merged commit 861f2b4 into master May 21, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant