First-class Bluetooth: core API, all ports, simulator debugging and build detection#5399
First-class Bluetooth: core API, all ports, simulator debugging and build detection#5399shai-almog wants to merge 30 commits into
Conversation
New com.codename1.bluetooth package (Java 5, NFC facade pattern): BluetoothUuid (core has no java.util.UUID), BluetoothError/Exception (typed errors + raw GATT status), AdapterState + listener, permission/ device-type/bond-state enums, abstract BluetoothDevice, Bluetooth entry facade with capability queries and EDT-dispatched adapter events. Impl hooks: CodenameOneImplementation.getBluetooth() (null default) + Display.getBluetooth(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
com.codename1.bluetooth.le: BluetoothLE base with a core scan registry that multiplexes any number of concurrent BleScan handles over one platform scan (per-handle filter demux + duplicate suppression); BlePeripheral with the per-peripheral GattOperationQueue (serialized do* SPI, safety timeout so lost platform callbacks never wedge the queue), connection lifecycle with typed events, CCCD-multiplexed subscriptions, MTU/RSSI/priority/bond ops and L2CAP channels. com.codename1.bluetooth.gatt: client-side GattService/Characteristic/ Descriptor model with convenience ops routed through the owning peripheral's queue. AdvertisementData includes a pure-core AD-structure parser. Bluetooth.getLE() no-op fallback wired. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
com.codename1.bluetooth.le.server: GattServer with EDT-dispatched GattServerListener (+ GattServerAdapter that fail-fast rejects unhandled requests), local GATT model (GattLocalService/Characteristic/ Descriptor), read/write request envelopes, BleCentral and advertising types (AdvertiseSettings/Data, BleAdvertisement). Isolated in its own package so build detection can key ADVERTISE permissions on the prefix. com.codename1.bluetooth.classic: BluetoothClassic facade (discovery, bonding, discoverable request, RFCOMM connect/listen), ClassicDiscovery handle, stream-based RfcommConnection/RfcommServer. BluetoothLE gains the peripheral-role entry points (openGattServer, startAdvertising, openL2capServer); Bluetooth.getClassic() fallback wired; package-info docs for all five packages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bytecode-scanner detection for com.codename1.bluetooth keyed on the permission-aligned package layout: scan/connect/peripheral/classic flags (+ facade-method refinement via usesClassMethod). Android injection goes through the new testable BluetoothManifestFragments helper handling the Android 12 split (BLUETOOTH_SCAN neverForLocation flag per the android.bluetooth.neverForLocation hint default true, legacy perms capped at maxSdkVersion 30, BLUETOOTH_CONNECT/ADVERTISE, bluetooth_le uses-feature with android.bluetooth.required hint) with quote-delimited dedup safe against old-BLE-cn1lib merged xpermissions. iOS: CoreBluetooth weak-link + CN1_INCLUDE_BLUETOOTH define flip + opt-in ios.bluetooth.background merge into ios.background_modes; NSBluetooth* plist defaults ride the new AiDependencyTable entry (Android perms deliberately absent there). ANDROID_PERMISSIONS whitelist gains the Android 12 constants. 10 new fragment tests + 2 table tests, all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndroidBluetooth facade (adapter-state receiver, Android 12 runtime permission split with string-literal S+ constants, requestEnable via intent-result) + AndroidBluetoothLE (scan multiplex over BluetoothLeScanner, peripheral cache, connected/bonded lookups) + AndroidBlePeripheral GATT client (single-pending-slot callbacks under the core op queue, CCCD writes complete doSetNotifications, immediate onCharacteristicChanged buffer copy, gatt.close() always after disconnect) + AndroidGattServerImpl (serialized onServiceAdded / onNotificationSent queues, auto-injected CCCD, static-value serving, long-read slicing) + AndroidRfcomm classic (discovery receivers, discoverable intent, socket connect/accept on daemon threads) + AndroidL2capCompat (cached reflection for API-29 L2CAP against the API-27 compile jar). Wired via AndroidImplementation.getBluetooth(). Core doc notes: fireScanFailed cleanup contract + Android getConnectedPeripherals filter approximation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scripted FakeBluetooth/FakeBluetoothLE/FakeBlePeripheral fakes (synchronous-on-demand: nothing completes until the test drains the pending-op queue; scriptable failures) installed via TestCodenameOneImplementation.setBluetooth + reset hook. Ten suites: fallback contract (no-op base instances), BluetoothUuid, raw advertisement parsing, scan demux (multi-handle filters, dedup, last-stop platform teardown, scan-failed fan-out), connection lifecycle + service cache, op-queue serialization (cancel-skip, timeout without wedging), CCCD subscribe multiplexing, EDT delivery of every callback type, error mapping, adapter lifecycle. Full module: 4033 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Headless SimulatedBluetoothStack (single-scheduler engine: peripheral registry, per-op failNext deques, latency, event log, scan feeds, GATT client ops, app-side GATT server/advertising/L2CAP/RFCOMM registries, virtual central + virtual RFCOMM/L2CAP clients over piped streams, reset) with Auto/Manual schedulers (virtual clock, pump/ advance) and fluent VirtualPeripheral/Service/Characteristic/Descriptor builders. CN1 adapters behind the BleBackend SPI seam (future native btleplug backend plugs in there; switchBackend + cn1.bluetooth.backend property), SimBlePeripheral/SimGattServer/classic/RFCOMM/L2CAP adapters, JavaSEBluetooth with JavaSENfc-style sim flags, public BluetoothSimulator scriptable facade, JavaSEPort.getBluetooth wiring. 45 new deterministic tests incl. source-scan guards (no Swing/wall- clock in the stack core); javase module green at 124 tests. ScanResult.getTimestamp doc softened to ordered-not-wall-clock. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IOSBluetooth (request-id registry, CB-authorization permissions, lazy adapter monitor, dead-code-elimination touch block ordered after field init), IOSBluetoothLE/IOSBlePeripheral (13 do* methods over 32 new IOSNative declarations; aggregated one-shot GATT DB transfer; MTU from maximumWriteValueLength; iOS-semantics no-op bond/priority), IOSGattServer (local-id-keyed model, parked-CBATTRequest handle table, isReadyToUpdateSubscribers notify retry), advertising with Java-side timeout, L2CAP blocking streams over native handles. CN1Bluetooth.h/.m (~1900 lines, no ARC): whole body under #ifdef CN1_INCLUDE_BLUETOOTH with linkable #else stubs for all trampolines, serial dispatch queue, controller-lifetime CBPeripheral retention, NSString/NSData conversion before dispatch_async, tvOS/watchOS peripheral-role gating; commented define added to CodenameOne_GLViewController.h (flipped by builders). Both define paths compile (clang -fsyntax-only vs iphonesimulator + appletvsimulator SDKs); maven ios module green. Core follow-ups from port friction: ScanFilter getters (ports push filters to platform scanners - iOS background scanning needs them), platform notes on AdvertiseData (iOS drops manufacturer/service data) and GattServerListener descriptor requests (never fire on iOS). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hand-coded Swing debugger for the virtual Bluetooth stack: peripheral/
GATT JTree with an App-as-Peripheral root (advertising state, published
services, central subscriptions), card-based editors (adapter toggle,
latency, 12-op x BluetoothError failure injection, backend selector;
peripheral live editing + remote-disconnect; characteristic hex/UTF-8
value editor + push-notify), capped event-log table fed by
StackEventListener with coalesced tree refreshes and expansion
preservation, BluetoothSim.* prefs. Simulate-menu items in both menu
constructions. BluetoothSimulatorHooks (8 hooks incl. API-only
primeReadFailure; canonical demo peripheral AA:BB:CC:DD:EE:01 shared
with the toolbar) published via META-INF/codenameone/
simulator-hooks.properties -> CN.execute("bluetooth:itemN").
6 headless window tests; SimulatorHookLoaderTest fixture now hides
classpath hook files so its exact-count assertions stay hermetic.
javase module green at 130 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports/JavaSE/native/cn1-ble-helper: btleplug 0.11/tokio Rust helper
speaking line-delimited JSON (v1 protocol in PROTOCOL.md: capabilities
handshake, scan with full advertisement payloads, connect/discover/
read/write char+descriptor/subscribe/rssi(last-seen)/state events,
typed error codes; central-only). NativeBleBackend implements the
BleBackend SPI: helper resolution (cn1.bluetooth.helperPath property ->
bundled OS-keyed classpath resource -> PATH), reader/writer threads,
requestId correlation, crash-mid-flight -> typed IO_ERROR + adapter
UNSUPPORTED, shutdown hook; NativeBlePeripheral maps do* onto protocol
commands with the canonical-GATT-model pattern. JavaSEBluetooth
'native' switch now constructs it (runtime-toggleable vs simulator).
Packaging: optional cn1-binaries/ble/{macos,linux,windows} resource
mapping + opt-in -Pbuild-ble-helper cargo profile (default build
cargo-free). Tests: wire codec, resolution order, fake-helper
subprocess lifecycle (deterministic), hardware smoke gated on
-Dcn1.ble.smoke=true (verified live on this machine: POWERED_ON, 6
peripherals). javase module: 151 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JSBluetooth/JSBluetoothLE/JSBlePeripheral route the core SPI through 12 __cn1_bt_*__ host calls registered in browser_bridge.js (device/ attribute handle tables main-thread side; worker holds string ids + iids). Chooser-as-scan: startScan opens the browser device chooser built from the ScanSettings filters (all filter service uuids collected into optionalServices), delivers the single user-picked device as one ScanResult, then stays silently active; chooser cancel -> fireScanFailed(USER_CANCELED). One-shot full GATT DB discovery per connect; notifications copy DataView bytes before posting through the worker-callback channel; gattserverdisconnected -> typed connection event. requestDevice user-gesture handling: immediate attempt (relayed activation usually holds), SecurityError parks the job for the next real capture-phase gesture, 30s -> typed USER_CANCELED. All port.js bindings null-safe against stale translator-jar browser_bridge (typed NOT_SUPPORTED fallback, never a raw throw). MTU fixed 512, RSSI/bond/ L2CAP/peripheral/classic typed NOT_SUPPORTED. Verified: javac against real jars, node --check, and a full local hellocodenameone bundle build with the classes registered in translated_app.js. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New Bluetooth.asciidoc (NFC-chapter template): capability matrix per platform, six include-backed quick starts (scan+connect, read/write, notifications, peripheral mode, L2CAP, classic RFCOMM), auto-injected permissions + build-hints tables (neverForLocation beacon callout), simulator section (Bluetooth Simulation window, bluetooth:itemN hooks table, BluetoothSimulator scripting as a simulator-only listing, record-and-replay), testing section, closing platform-behaviour matrix. Six BluetoothJava00NSnippet classes compile in the docs/demos build. Validated: snippet-include gate (616 blocks), demos process-classes, asciidoctor, vale, LanguageTool, capitalization - all clean. Simulator screenshots (bluetooth-simulator-devices/log.png) to follow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Captured from the live Swing window: device/GATT tree with the characteristic value editor, and the adapter card (failure injection, backend selector, latency) over a populated event log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BluetoothFixture (versioned JSON model, hand-rolled writer + core JSONParser reader), FixtureScrambler (seeded pure function: stable synthetic ids, length-class-preserving names, same-length randomized payloads with company IDs kept, SIG UUIDs verbatim, memoized custom- UUID substitution, findLeaks no-PII invariant), FixtureRecorder (drives any BleBackend with bounded waits; optional GATT capture degrades to scan-only), FixtureCaptureMain CLI + scripts/bluetooth/ capture-fixture.sh (scrambles always; exit-3 leak gate; real ids to stderr only). SimulatedBluetoothStack.loadFixture replays the RSSI/ advertisement timeline on the stack scheduler (deterministic under ManualScheduler); BluetoothSimulator.loadFixture overloads; debug window gains Record from real hardware (dialog -> native backend -> scrambled import + optional save). VirtualPeripheral service-data/ tx-power; core AdvertisementData.getServiceDataUuids(). Shipped fixtures captured on real hardware via the Rust helper: ambient-scan.json (17 devices, rich RSSI timelines, Apple/Samsung manufacturer data, SIG service data) + ambient-scan-2.json (8 devices; GATT capture failed on all nearby devices - random-address privacy - so a second scan-only trace per spec), both leak-checked. 18 new deterministic tests incl. shipped-fixture invariant + full record->scramble->replay->GATT-read loop; javase module: 169 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CN1WebAuthn.m defined its three non-void trampolines without the ParparVM _R_<returnType> suffix (webauthnSupported__ vs the generated header's webauthnSupported___R_boolean etc.), so any app actually using WebAuthn would fail linking once CN1_INCLUDE_WEBAUTHN flips. Renamed all six definitions (real + stub paths) to the generated convention. IOSNfc's dead-code-elimination touch block ran before the REQUESTS/ TAGS field initializers (static init is textual order), making the sentinel callbacks synchronize on null during class init; moved the block after the fields with a comment pinning the order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Cloudflare Preview
|
The ble/ resource mapping copies the whole directory; exclude **/*.md so the maintainer docs that live next to the binaries in cn1-binaries stay out of every app's jar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The check-copyright-headers gate flagged 14 files. Thirteen genuinely lacked the GPLv2 + Classpath Exception header: the five new bluetooth package-info files (which followed com/codename1/nfc/package-info.java, predating the gate), the six new developer-guide snippets, and two pre-existing files this branch touches (TestCodenameOneImplementation, browser_bridge.js) that the gate checks because they are modified. The fourteenth, AndroidImplementation.java, has a complete and correct Codename One header and was failing purely because the file is CRLF: the stray carriage return defeated both the leading '/*' comparison and the '$'-anchored copyright-line regex. Rewriting a 15k-line file's line endings to satisfy the checker would be pure churn, so the checker now strips CR before matching -- it judges header text, not line endings. Verified it still rejects CRLF files with missing or bogus headers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Compared 145 screenshots: 145 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 149 screenshots: 149 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 146 screenshots: 146 matched. |
|
Compared 146 screenshots: 146 matched. |
|
Compared 145 screenshots: 145 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 145 screenshots: 145 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 176 screenshots: 176 matched. |
macOS ships a universal Mach-O binary covering x86_64 and arm64, but ELF
and PE have no fat-binary format, so a single per-OS binary left Linux on
ARM (Raspberry Pi, ARM servers) and Windows on ARM (Snapdragon X,
Surface) with no helper at all -- they silently reported the native
backend unavailable.
helperResourcePath now takes os.arch and resolves
ble/{linux,windows}/{x64,arm64}/; macOS keeps its single universal file.
normalizeArch maps the aliases (amd64/x86_64/x64 -> x64,
aarch64/arm64 -> arm64) and returns null for architectures with no
bundled binary (32-bit x86/ARM), which falls through to the PATH lookup
instead of failing; the resolution trace now reports the unmatched
os.arch. Tests cover the arch keying, macOS ignoring arch, the alias
matrix and the PATH fall-through: javase module 172 green.
Binaries land in the companion cn1-binaries PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three failure classes the Ant/CLDC11 build and SpotBugs caught but the Maven build did not (Maven compiles core against the full JDK and my mvn test runs never invoked SpotBugs): Device API surface (CLDC11.jar is signature-only): Character.forDigit does not exist on device -> BluetoothUuid uses an explicit hex[] table; java.util.Timer has only the no-arg constructor -> GattOperationQueue schedules each timeout on its own Timer (mirroring Display.setTimeout) and cancels both task and timer, and BlePeripheral tracks its connect timer to cancel it. This broke the Ant core build and every downstream iOS/Mac native job (ParparVM emitted a call to the absent java_lang_Character_forDigit). SpotBugs (runs in the core-unittests verify phase, not in mvn test): five SIC_INNER_SHOULD_BE_STATIC_ANON dispatch Runnables/TimerTask moved into static helpers so they carry no synthetic outer reference (the NFC code's established pattern), and one DM_DEFAULT_ENCODING - the UTF-8 advertisement decoder now throws instead of falling back to the platform default encoding, which would corrupt names. core Ant build green; core-unittests spotbugs 0 bugs; 4033 + 172 tests still green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IOSBluetooth.bytesToHex used Character.forDigit, which vm/JavaAPI does not define, so ParparVM would emit a call to an undeclared java_lang_Character_forDigit C function and fail the Xcode build -- the same break the core BluetoothUuid fix addressed, in the iOS port. The iOS jobs failed on core BluetoothUuid first, so this would have surfaced on the next run; caught it by scanning the port Java against vm/JavaAPI. Character.digit (used in hexToBytes) IS present, so that path is unchanged. new Timer(true) in IOSBleAdvertisement is fine -- vm/JavaAPI declares the daemon constructor (only CLDC11, which core compiles against, lacks it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nine anonymous inner classes (permission/enable/discoverable Runnables, an AdvertiseCallback, RFCOMM connect/listen/L2CAP-server Thread bodies, the discovery ClassicDiscovery + BroadcastReceiver) used only static references and captured final locals, never the enclosing instance, so each held an unused synthetic outer reference. Extracted every one into a private static factory taking the captured locals as final params (the BlePeripheral.connectTimeoutTask pattern); behavior, threading and ordering unchanged. android module SpotBugs: 9 -> 0, matching the port's clean baseline. The android spotbugs is report-only (failOnError=false) so this didn't fail CI, but it was a regression in the quality report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # vm/ByteCodeTranslator/src/javascript/browser_bridge.js
|
Compared 12 screenshots: 12 matched. |
|
Compared 143 screenshots: 143 matched. |
|
Compared 148 screenshots: 148 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Apple Watch (watchOS / Core Graphics)Compared 216 screenshots: 215 matched, 1 missing actual.
|
CI's static-analysis gate (generate-quality-report.py forbidden_pmd_rules) failed on 65 forbidden PMD violations in the new core Bluetooth code -- a gate mvn test does not run. Fixed: 53 MissingOverride (added @OverRide throughout, incl. interface impls -- the core Ant build is javac.source=1.6 so this compiles), 4 ForLoopCanBeForeach (indexed loops -> enhanced-for where the index was unused), 3 UnnecessaryConstructor (removed empty holder/no-op constructors; the compiler-generated default is identical), 3 CompareObjectsWithEquals (deliberate identity == on in-flight op/ registration objects, kept with the repo's //NOPMD convention), 1 PreserveStackTrace (chain the cause in AdvertisementData.utf8), 1 UnnecessaryImport. PMD re-run: 0 forbidden; ant jar + core-unittests 4033 tests + 0 SpotBugs green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New maven module codenameone-native-ble (package com.codename1.bluetooth. helper, dep: core only): the transport-agnostic cn1-ble-helper client so the native Win32/Linux ports can reuse it. HelperTransport abstraction (start/readLine/writeLine/close), BleBackend SPI, transport-agnostic HelperBleBackend (Wire JSON protocol + PendingOp + reader thread) and HelperBlePeripheral, plus MockHelperTransport for subprocess-free tests. All runtime types constrained to vm/JavaAPI (ParparVM translates the module for the native ports): ConcurrentHashMap -> synchronized HashMap/ HashSet, java.util.Base64 -> hand-rolled translatable codec in Wire, classpath-extraction HelperBinaryResolver moved to JavaSE (JVM-only). JavaSE refactor (unchanged behaviour, 180 tests green): NativeBleBackend is now a thin HelperBleBackend subclass supplying a ProcessTransport (the one place ProcessBuilder lives on JavaSE); SimulatorBleBackend/fixtures repointed to the module. Module: 14 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
These native ports have full threading but no java.lang.ProcessBuilder (absent from vm/JavaAPI), so add a small child-process bridge for the shared cn1-ble-helper client to spawn/talk to the helper: procSpawn/ procRead/procWrite/procCloseStdin/procClose/procIsAlive on LinuxNative + WindowsNative, implemented in nativeSources over posix_spawn+pipes (cn1_linux_subprocess.c) and CreateProcess+anonymous pipes (cn1_windows_subprocess.c). Modeled line-for-line on the existing socket bridge: ParparVM mangled C names, CN1_YIELD_THREAD/RESUME around every blocking syscall + a keep-alive anchor on byte[] buffers, opaque long handles, manual malloc/free. Java compiles on both ports (mvn -pl maven/linux,maven/windows compile); the C is CI-native-build verified (Linux .c passes gcc -fsyntax-only against stubbed headers). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires HelperBluetooth (the shared module's transport-agnostic facade) over each port's native subprocess bridge, giving the native desktop ports real BLE central (scan / connect / GATT client / notifications) via the cn1-ble-helper subprocess -- btleplug -> BlueZ on Linux, WinRT on Windows. Linux/WindowsBluetoothTransport frame the shared NativeSubprocessTransport onto the port's proc* natives; Linux/WindowsImplementation.getBluetooth() returns HelperBluetooth (replacing the no-op fallback these ports inherited). The helper is resolved by the cn1.bluetooth.helperPath property else the bare name via the OS PATH search (posix_spawnp/CreateProcess). Peripheral/L2CAP/classic report unsupported (btleplug is central-only). jar-with-dependencies folds codenameone-native-ble into each port jar for ParparVM translation; both ports compile locally. The module's FixtureReplayE2ETest drives a scrambled real capture (all 8 devices) through this exact helper stack + a connect/discover/read over a mock transport -- no hardware. Fixes the earlier gap where these ports were stubbed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Compared 142 screenshots: 142 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
The com.codename1.bluetooth.helper client (HelperBleBackend, transports, Wire codec, HelperBluetooth/LE facades) lived in the maven-only native-ble module. The Ant core build compiles the native ports against CLDC11 and cannot see Maven modules, so JavaSEBluetooth and the native Linux/Windows ports failed with "package com.codename1.bluetooth.helper does not exist". Core is the only artifact both build systems share, so the helper now lives there. - Relocate the 10 helper client classes into CodenameOne/src/com/codename1/bluetooth/helper/ (compiled against the CLDC11 device API: no Thread.setDaemon, hand-rolled Base64 via Wire). - Move the mock transport + Wire/backend/fixture-replay tests into core-unittests; delete the native-ble module and drop its registration from the root pom and the javase/linux/windows port poms. - Fix 4 SpotBugs SIC_INNER findings surfaced now that the helper is scanned under core-unittests: share a static NO_OP PendingOp for fire-and-forget scanStop, make booleanOp static, and extract the duplicated characteristic/descriptor read completion into a static bytesOp factory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Compared 147 screenshots: 147 matched. Benchmark Results
Detailed Performance Metrics
|
The check-package-info gate requires every documented package under CodenameOne/src to carry a package-info.java. The helper client only came under that scan once it moved from the native-ble module into core, so the gate failed with "com/codename1/bluetooth/helper: missing package-info.java". Document the package as internal port-support (not public API) to match the rest of the bluetooth tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Java-25 markdown-docs gate (validate-java25-markdown-docs.sh) rejects classic /** javadoc markers anywhere under CodenameOne/src. The helper client used /** because the native-ble module it came from was not scanned; now that it lives in core, convert its doc comments to the /// markdown style the rest of the tree uses. Comment-only change -- core still compiles and the gate passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bluetooth.helper client came from the native-ble module, whose PMD config was laxer than the forbidden-rule gate that scans CodenameOne/src. Once relocated into core it tripped 79 forbidden violations: - 77 MissingOverride: add @OverRide to every method that implements an interface (BleBackend, HelperTransport, PendingOp/ScanSink anonymous classes) or overrides a BlePeripheral SPI method. Core compiles with -source 1.6, so @OverRide on interface implementations is legal; the clean compile confirms none were added to a non-overriding method. - 2 CompareObjectsWithEquals: the transport-identity and scan-sink-identity checks are deliberate reference comparisons, suppressed with //NOPMD CompareObjectsWithEquals. Also fix a genuine test race in HelperBleBackendMockTransportTest: the connect future errors/completes synchronously, but the peripheral's CONNECTING->DISCONNECTED/CONNECTED transition is delivered on the EDT, after isDone() is already true. On the helper-crash path failAllPending() fails the connect op before handleHelperDied() fires DISCONNECTED, so a test thread that reads getConnectionState() right after isDone() can still observe CONNECTING. Await the state transition instead of asserting it synchronously, matching the adapter/state awaits already used in this suite. Production behaviour is correct -- an app's own connect callback runs on the EDT after the state has settled. Verified 8/8 deterministic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes Bluetooth a first-class citizen of the framework: a typed, Java-idiomatic API in
com.codename1.bluetoothwith deep native support on every platform we can reach, a scriptable simulator with a real debugging UI, automatic permission/dependency injection in the builders, developer-guide coverage and tests throughout.Today's only Bluetooth story is the
bluetoothle-codenameonecn1lib — a port of a Cordova plugin. It carries the web-shaped design that made sense there and not here: results arrive as untypedMaps parsed from JSON strings, callbacks are correlated by method-name strings (so only one call per operation can be in flight), values travel base64-encoded, and errors are map keys rather than typed exceptions. It also only ever exposed BLE central: peripheral mode and bonding sat unbridged in the vendored native code, L2CAP and Classic Bluetooth were absent, and only Android/iOS had real implementations. That cn1lib is left untouched by this PR.The API
Follows the NFC facade pattern —
Bluetooth.getInstance()→Display.getBluetooth()→CodenameOneImplementation.getBluetooth()(null default), with no-op fallbacks so app code never null-checks or branches on platform.Packages are split by role and aligned to the permission model so build-time detection can be fine-grained:
com.codename1.bluetooth(facade,BluetoothUuid, typedBluetoothError/BluetoothException, adapter state, permissions),.le(scanning, GATT client, L2CAP),.le.server(GATT server + advertising — referencing this package is what earnsBLUETOOTH_ADVERTISE),.gatt(client model),.classic(discovery + RFCOMM). All core code is Java-5-compatible; listener interfaces are single-method so app code can use lambdas.The cn1lib's structural flaws are fixed rather than papered over:
AsyncResource. A per-peripheral operation queue serializes GATT ops toward the platform stack (Android silently drops concurrent requests) with a safety timeout, so a lost platform callback fails one operation instead of wedging the connection. Ops on different peripherals run fully concurrently.BleScanhandles share one platform scan; core demultiplexes results per handle's filters with per-handle duplicate suppression, and stops the platform scan when the last handle stops.BluetoothExceptioneverywhere; unsupported features fail fast withNOT_SUPPORTEDand are branchable up-front via capability queries (isLeSupported,isClassicSupported,isPeripheralModeSupported,isL2capSupported).Platform coverage
AndroidBluetooth+ GATT client/server, RFCOMM, andAndroidL2capCompat(reflection for the API-29 L2CAP symbols against the API-27 compile jar). Android-12 runtime permission split;onCharacteristicChangedbuffers copied immediately;gatt.close()always after disconnect.CN1Bluetooth.h/.m(~1900 lines, no ARC) gated behindCN1_INCLUDE_BLUETOOTHwith linkable#elsestubs, so apps that never touch Bluetooth link no CoreBluetooth symbol. Dedicated serial dispatch queue, controller-lifetimeCBPeripheralretention, peripheral role compiled out on tvOS/watchOS. Both define paths verified withclang -fsyntax-onlyagainst the iphonesimulator and appletvsimulator SDKs.Ports/JavaSE/native/cn1-ble-helper, line-delimited JSON over stdin/stdout). The default build never touches cargo; prebuilt binaries come from cn1-binaries (companion PR) and a missing helper degrades to "backend unavailable".__cn1_bt_*__host calls). Web Bluetooth is chooser-based rather than scan-based, sostartScanopens the browser chooser and delivers the single user-picked device; the user-gesture requirement is handled by parking and re-firing on the next real gesture. Divergences (fixed MTU, no RSSI/peripheral/classic/L2CAP) are typed and documented.Simulator debugging
Simulate → Bluetooth Simulationopens a device/GATT tree (including an "App as Peripheral" node showing your own GATT server, advertising state and live subscriptions), a hex/UTF-8 characteristic value editor with push-notify, adapter toggle, latency control, failure injection (any operation × anyBluetoothError), a backend selector, and an event log of every Bluetooth call the app makes.The same world is scriptable from tests/app code via
BluetoothSimulator, and drivable cross-platform throughCN.execute("bluetooth:itemN")hooks.Record and replay: capture real Bluetooth traffic from the host radio, run it through a deterministic, structure-preserving PII scrambler (synthetic addresses,
Device-XXXXnames, same-length randomized payloads, SIG UUIDs kept, custom UUIDs consistently remapped), and replay it through the whole stack in tests. Two real ambient scans (17 and 8 devices) are captured and shipped as fixtures; capture is available from the CLI (scripts/bluetooth/capture-fixture.sh) and the debug window's "Record from real hardware" button. Fixtures are leak-checked at capture time and in CI.Build-time detection
The bytecode scanner keys on the permission-aligned package layout, so a central-only app never carries
BLUETOOTH_ADVERTISEand a non-Bluetooth app sees no manifest change at all:com.codename1.bluetoothBLUETOOTH(capped at API 30),bluetooth_lefeatureNSBluetooth*UsageDescriptiondefaults, CoreBluetooth link,CN1_INCLUDE_BLUETOOTHBLUETOOTH_SCAN(+neverForLocation),BLUETOOTH_ADMIN,ACCESS_FINE_LOCATIONBLUETOOTH_CONNECTcom.codename1.bluetooth.le.serverBLUETOOTH_ADVERTISE(+CONNECT)bluetooth-peripheralbackground modecom.codename1.bluetooth.classicbluetoothfeatureNew hints:
android.bluetooth.neverForLocation(default true — beacon apps that derive location from scans must set it false),android.bluetooth.required,ios.bluetooth.background. Fragment assembly lives in the testableBluetoothManifestFragments; dedup is quote-delimited, soBLUETOOTH_SCANcan't be suppressed by a pre-existingBLUETOOTH(the exact trap a naivecontainswalks into) and projects still carrying the old cn1lib's mergedandroid.xpermissionsdon't get duplicates. The BuildDaemon twin is in a companion PR.Tests
All deterministic by construction — fakes complete only when a test drains them, and the virtual stack runs on a manual scheduler with a virtual clock (source-scanned to keep wall-clock out of it).
core-unittests: 4033 green — 10 new suites covering the fallback contract, UUIDs, raw advertisement parsing, scan demux, connection lifecycle, op-queue serialization/timeout, CCCD multiplexing, EDT delivery of every callback type, and error mapping.javase: 169 green — virtual stack, GATT, peripheral mode, RFCOMM/L2CAP streams, failure scripting, fixture scrambler invariants and recorded-trace replay, headless window checks.codenameone-maven-plugin: 268 green — detection matrix including the substring-dedup regression.Also in this PR
Two latent iOS bugs found while building the port and fixed here:
CN1WebAuthn.mdefined its non-void trampolines without ParparVM's_R_<returnType>suffix (webauthnSupported__vs the generatedwebauthnSupported___R_boolean), so any app actually using WebAuthn would have failed to link once the define flipped.IOSNfc's dead-code-elimination touch block ran before its field initializers (static init is textual order), so the sentinel callbacks synchronized on a null map during class init.Follow-ups (not in this PR)
ios.bluetooth.backgroundplus service-filtered scans; CoreBluetooth state restoration is deliberately out of scope for v1.🤖 Generated with Claude Code