Skip to content

Commit 930f8d6

Browse files
shai-almogclaude
andauthored
Native Apple Watch (watchOS) port: Core Graphics backend + watch app target (#5252)
* feat: smartwatch form-factor support (Apple Watch + Wear OS) Adds the foundation for building Apple Watch / Wear OS apps and proves the model end-to-end in the hellocodenameone screenshot suite. Form-factor API (core + ports): - Display.isWatch()/getFormFactor() + CN facades + FORM_FACTOR_* constants, following the isTablet()/isDesktop() pattern. Defaults false in CodenameOneImplementation; overridden in iOS (native isRunningOnWatch), Android (FEATURE_WATCH), and JavaSE (skin watch=true). New "watch" resource override layer in each port's getPlatformOverrides(). watchOS rendering port (iOS native sources, all additive under #if TARGET_OS_WATCH so the iOS slice is byte-for-byte unchanged): - CN1CGGraphics: Core Graphics rasterizer backend (no GL/Metal on watchOS). - CN1WatchRenderingView: CGBitmapContext surface conforming to CN1RenderingView. - CN1WatchHost: timer-driven frame pump + crown/tap input bridge. - FillRect/DrawRect/ClearRect/DrawLine wired to the CG backend (rollout in WATCHOS_PORT.md). Validated on the watchOS simulator (Xcode 26.3). Build pipeline (separate watch entry point + seamless double app): - codename1.watchMain in codenameone_settings.properties declares a distinct watch lifecycle (Apple Watch + Wear); a distinct entry enables more aggressive tree-shaking. Flows via CN1BuildMojo as the watchMain build arg. - WatchNativeBuilder (modeled on MacNativeBuilder) auto-enables when watchMain is set and adds a watchOS app target (arm64_32, WKApplication, companion embed or standalone) to the iOS Xcode project via the xcodeproj gem. - watchNative.* build hints registered in BuildHintSchemaDefaults. - JavaSE ships generated Apple Watch simulator skins (round, safe areas). hellocodenameone (proves the APIs + model): - AbstractGraphicsScreenshotTest renders 4 separate full-screen captures on a watch (via CN.isWatch()) instead of a cramped 2x2 grid; BaseTest gains a chained-capture helper. Verified in the JavaSE simulator with the Apple Watch skin (396x484): graphics-{draw,fill}-arc-{direct,image}-aa-{off,on}. - HelloCodenameOneWatch watchMain lifecycle + codename1.watchMain setting. - Cn1ssDeviceRunner gains an optional -Dcn1ss.filter subset selector. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): WatchNativeBuilder emits a watchOS target that compiles (validated) The watch target now injects into the regular ios-source build and compiles against the watchOS SDK up to the GL-call link boundary. Validated end-to-end by building hellocodenameone for the watchOS simulator (Xcode 26.3 / watchOS 26.2): the entire ParparVM-translated app + nativeSources + the Core Graphics backend compile; the watch target is created, the SwiftUI @main shell + surface bridge + Digital Crown/tap forwarding compile, and the bridging header resolves. - CN1ES2compat.h: guard `#define CN1_USE_METAL` with `#if !TARGET_OS_WATCH` so the watch slice never activates the Metal backend (watchOS has no Metal). - WatchNativeBuilder: - Complete the generated CN1WatchApp.swift (CN1WatchRootView + the CN1WatchSurface frame bridge + crown/tap), not just an App stub. - writeStubHeaders(): emit watchOSStubs/ GLKit + OpenGLES stub headers (same pattern MacNativeBuilder uses for Catalyst) so the shared sources that import those frameworks compile; HEADER_SEARCH_PATHS points the watch target at them. - SDK-conditional ARCHS: arm64_32 for watchos*, $(ARCHS_STANDARD) for watchsimulator* (arm64_32 is device-only; the simulator slice was failing "Unable to find module 'Swift'" trying arm64_32-simulator). - Exclude CN1MetalShaders.metal from the watch target. - Raise the watchOS floor to 10.0 (SwiftUI onChange(of:) two-param API). - IPhoneBuilder: call writeStubHeaders alongside the watch Info.plist + entry. Remaining for a linking/running watch app (tracked in WATCHOS_PORT.md): wire the remaining ExecutableOps to CN1CGGraphics and guard the GL call sites in CodenameOne_GLViewController.m so no OpenGL ES symbols are referenced on watch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): wire transform + polygon ops to the Core Graphics backend Scale/Rotate/ResetAffine/FillPolygon get a #if TARGET_OS_WATCH execute branch routing to CN1CGGraphics (scale/rotate/reset-affine/fill-polygon), with the GLKit-math init paths guarded out on watch. Joins the earlier FillRect/DrawRect/ ClearRect/DrawLine. Remaining ops + the GL no-op shim for CodenameOne_GLViewController and the watch runtime bootstrap are next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): wire DrawString + DrawGradient to the Core Graphics backend #if TARGET_OS_WATCH execute branches: CN1CGDrawString (Core Text) and CN1CGGradientRect. 10 of the ExecutableOps now route to CN1CGGraphics on watch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): guard MessageUI/AudioToolbox (absent on watchOS) Guard the MessageUI (mail/SMS composer) + AudioToolbox imports, the matching GLViewController delegate conformances/callbacks, the sendEmail/sendSMS native methods, and the vibrate AudioServices call behind #if !TARGET_OS_WATCH. Confirms the architecture: watchOS ships UIKit headers but marks UIView/UIViewController/UIEvent/CADisplayLink unavailable, so the UIViewController-based CodenameOne_GLViewController cannot compile for watch -- the watch slice needs a CG render-driver replacing it (next). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(watch): scope the watch render-driver contract (55 native fns + singleton) Document the exact contract the watch CG render-driver must satisfy to replace the UIViewController-based GLViewController on watchOS, modeled on LinuxPort. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): watch render-driver replacing the GL view controller CodenameOne_GLViewController.h gains a #if TARGET_OS_WATCH NSObject interface variant (no UIViewController); CN1WatchViewController.m implements it as the watch render-driver: the ExecutableOp queue + flushBuffer swap + drawFrame draining into the Core Graphics surface (CN1WatchRenderingView). The GL view controller + UIApplication delegate are excluded from the watch target. Native-method surface (the 55 Java_*Impl entry points) follows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): guard EAGLView/UIWindow, exclude UIWebView peer, guard net indicator EAGLView.h (CAEAGLLayer UIView) guarded off for watch; render-driver eaglView returns id; CN1ShowLaunchPlaceholder(UIWindow*) guarded; UIWebViewEventDelegate.m excluded; NetworkConnectionImpl network-activity-indicator calls guarded. watch compile errors down from 36 to 1 (AudioToolbox next). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): Core Graphics op rollout + IOSNative watchOS guards Route image/transform/clip/path/alpha-mask ops to the Core Graphics backend on watchOS (DrawImage, TileImage, SetTransform, ClipRect, DrawPath, DrawTextureAlphaMask), add CN1CGFillAlphaMask + a heap-backed alpha-mask "texture" so anti-aliased shapes render without GL/Metal. Inline GLKit matrix math into the watch GLKit stub so the transform bookkeeping compiles. Guard the file-scope GL shader helpers and the broad UIKit-peer surface in IOSNative.m (clipboard, text peers, media, push, location, biometrics, screen-capture, CIFilter, UIApplicationMain) behind #if !TARGET_OS_WATCH with no-op watch bodies; the iOS path is preserved verbatim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): exclude/guard UIKit peer components on watchOS Make the tap-gesture, inline text-field/text-view, web-view and AudioQueue peers empty under #if !TARGET_OS_WATCH (headers and impls) and add their .m files to the watch source-exclusion list. Guard the AudioPlayer remote-control UIApplication call. iOS code preserved verbatim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): compile GLViewController graphics primitives on watchOS Stop excluding CodenameOne_GLViewController.m from the watch slice so its shared CGContext/op-based graphics entry points (createImage, fonts, the *Impl drawing functions, safe-area + status-bar helpers) link on watch. Guard the UIViewController class + UIKit-only helpers (launch placeholder, status-bar tap proxy, editingComponent, orientation/keyboard) behind #if !TARGET_OS_WATCH; the watch render-driver class lives in CN1WatchViewController.m. iOS path preserved verbatim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): watch runtime glue + bootstrap -> watch app links Add CN1WatchRuntime.m: the app-agnostic half of the watchOS bootstrap (cn1_watch_runtime_start -> initConstantPool + app hook; runtime_paint drives the render-driver drawFrame; runtime_pointer* feed CN1 pointer events) plus no-op stubs for the watch-excluded 3D GL bridge and the app-suspend globals owned by the iOS app delegate. With the per-file main() rename of the ParparVM phone Stub on the watch target, the HelloCodenameOne watch app now compiles AND links for watchsimulator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): boot CN1 runtime + render real forms to Core Graphics Resolve the watch bootstrap deadlock and render the live UI: - run the VM bootstrap (Stub.main -> Display.init) on a dedicated thread and make watch initVM mirror UIApplicationMain (schedule the lifecycle callback, then block forever) so Display.init never falls through to the blocking initDefaultUserAgent path iOS never reaches. - wire getDisplayWidth/HeightImpl to the CN1WatchRenderingView logical size on watch (the UIView metric code is excluded) so forms lay out and paint. - implement watch screenshot() by PNG-encoding the CG surface and feeding onScreenshot, replacing the null-callback placeholder. - fall back to a system font in string/charWidthNativeImpl when the font peer is nil (avoids an NSDictionary-with-nil crash on the first text measure). The HelloCodenameOne cn1ss suite now boots on the watch simulator and captures real rendered frames (verified: "Main Screen" form with title + fields). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): builder generates working watch target (bootstrap + main rename) Backport the project mutations proven locally into WatchNativeBuilder so a regenerated project boots: - emit cn1_watch_app_main() into CN1WatchBootstrap.m, entering the regular main class's generated Stub.main (which drives Cn1ssDeviceRunner on watch). - stop excluding the phone Stub from the watch target; instead neutralise its duplicate C main() with a per-file -Dmain rename (+ return-type warning suppression), keeping the app's translated classes + Stub.main available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(watch): install SIGSEGV->NPE handlers on the watch slice Mirror CodenameOne_GLAppDelegate's installSignalHandlers (that file, the UIApplication delegate, is excluded on watchOS) so a stray null/dangling deref in a native/peer path converts to a recoverable Java exception instead of taking the whole watch app down, matching the iOS runtime's behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): leak native peers instead of releasing dangling pointers deleteNativePeer's [release] of a GC-handed-back peer pointer crashed with a pointer-auth fault (use-after-free) and stalled the suite after ~2 tests. No-op the release on the watch slice (leak peers) so the screenshot suite runs to completion; peer-lifecycle hardening tracked separately. With this the full hellocodenameone cn1ss suite captures 160 screenshots on watchsimulator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(watch): add watchOS screenshot goldens (160 captures) Initial watch golden set captured from the hellocodenameone cn1ss suite on the watchOS simulator via the Core Graphics backend (streamed through the standard Cn1ssScreenshotServer WS sink). The watch form factor splits the iOS 2x2 graphics-op grid into separate full-screen tests, so the set is larger than the iOS golden set. Spot-verified (Main Screen form, chart-pie, graphics clip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(watch): pin watch goldens to the steady-state capture set (160) The initial commit accumulated 209 PNGs across several local runs; a clean single run of run-watch-ui-tests.sh reproduces 160 (0 mismatch). Pin the golden set to exactly that steady-state output so the pipeline reports 0 missing / 0 mismatch on a healthy run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(watch): add run-watch-ui-tests.sh (build watch target, run suite, compare) Lean watch screenshot runner: builds the <Main>Watch target for watchsimulator, boots a watch sim, launches the app (which streams frames to the standard Cn1ssScreenshotServer WS sink over ws://127.0.0.1:8765), waits for the suite to settle, then reuses cn1ss_process_and_report to compare against the watch golden set and post the PR status comment. Validated locally: 160 equal, 0 mismatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): drop redundant getFormFactor; exclude CN1WatchApp.swift from iOS - Remove Display.getFormFactor()/CN.getFormFactor() + FORM_FACTOR_* constants: redundant with isWatch()/isTablet()/isDesktop() (no consumers), and the source of the checkstyle if-whitespace failures. - WatchNativeBuilder: add CN1WatchApp.swift (SwiftUI @main, imports WatchKit) to the iOS app target's EXCLUDED_SOURCE_FILE_NAMES. The watch entry file is globbed into the iOS/Mac-Catalyst app target during project generation, and WatchKit doesn't exist there -> the iOS screenshot build failed to compile it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(watch): run the watchOS screenshot suite in the iOS workflow Add a watchOS UI screenshot step to the build-ios job (the GL job already generates the project containing the auto-enabled watch target): ensure a watchOS simulator runtime, then run run-watch-ui-tests.sh which builds the watch target, runs the cn1ss suite on the watch sim, streams to the WS sink, and compares against scripts/ios/screenshots-watch (gating: fail on mismatch, tolerate up to 4 missing). Validated locally: 160 equal, 0 mismatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): drop getFormFactor usage from the watch sample (compile break) HelloCodenameOneWatch.kt referenced Display.formFactor (Kotlin sugar for the now-removed getFormFactor()), which broke the hellocodenameone-common compile on every platform that builds the sample suite (Android/JS/iOS/suite-classes). Log isWatch() only; refresh the stale getFormFactor mentions in comments/KDoc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): guard CN1WatchApp.swift with #if os(watchOS) EXCLUDED_SOURCE_FILE_NAMES did not reliably keep the generated SwiftUI entry out of the iOS/Mac-Catalyst app target (it is globbed into <mainClass>-src/), so the iOS screenshot build kept failing on `import WatchKit`. Wrap the whole generated file in #if os(watchOS) so it is an empty translation unit everywhere except watchOS, independent of target membership. Watch target still builds (verified); on iOS it now compiles to nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(watch): promote watch screenshots to a dedicated build-ios-watch job Move the watchOS screenshot run out of the build-ios job into its own build-ios-watch job (mirroring build-ios-metal), so it shows up as a distinct check, posts its own PR status comment (CN1SS_IOS_WATCH_COMMENT marker), and a watch regression doesn't mask the iOS GL result. Builds the sample (which generates the watch target), ensures a watchOS sim runtime, then runs run-watch-ui-tests.sh against scripts/ios/screenshots-watch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): decouple watch target from iOS app build; select Xcode 26 in runner Two CI breakages: - build-ios failed because the companion embed makes the iOS app depend on the watch target, so building the iOS app also builds the watch target against the iphonesimulator SDK -- where the real OpenGLES framework collides with watchOSStubs (duplicate EAGLContext). Make companion embedding opt-in (watchNative.embedCompanion=true, default off); the watch slice is built and tested independently, so the iOS screenshot build no longer touches it. - build-ios-watch ran under the runner's default Xcode 16.4; run-watch-ui-tests.sh now selects Xcode 26 (watchOS 26 SDK + arm64 watch sim) like build-ios-app.sh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): gate the WKWebView/WebKit import off on watchOS ENABLE_WKWEBVIEW is on by default in IPhoneBuilder, so the watch build pulled in <WebKit/WebKit.h> (unavailable on watchOS) -> 'WebKit/WebKit.h' file not found. My local watch project predated the default, which is why it only surfaced in CI. Guard the import with !TARGET_OS_WATCH; that also leaves supportsWKWebKit undefined so the WK usage block compiles out on watch too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(watch): pin a 46mm watch simulator to match the goldens Screenshots are pixel-compared, so the sim screen size must match the golden-capture device (46mm Apple Watch, 416x496). The runner now prefers a 46mm model (override via CN1SS_WATCH_MODEL/CN1SS_WATCH_UDID) and logs the available watch sims, instead of grabbing whichever Apple Watch is listed first (which could be a different size -> every screenshot mismatches). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): undef CN1_USE_AVKIT on watchOS (AVKit/AVPlayerViewController absent) IPhoneBuilder uncomments //#define CN1_USE_AVKIT for all targets, pulling in the AVKit AVPlayerViewController video path -- unavailable on watchOS (the iOS screenshot build hit AVPlayerViewController/UIPageViewController errors). Undo the define on the watch slice at the define site so all AVKit blocks compile out; the watch video stubs already return defaults. Reproduced CI's exact macro state locally (ENABLE_WKWEBVIEW + CN1_USE_AVKIT + CN1_INCLUDE_CRYPTO on) and the watch target now builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): guard camera + local-notification watchOS-unavailable APIs With the iOS sample's real build hints reproduced locally (INCLUDE_CAMERA_USAGE + CN1_INCLUDE_NOTIFICATIONS/2 + AVKit + WKWebView + crypto all on), the watch target hit three more unavailable APIs: - captureCamera: UIImagePickerController / UIPopoverController / presentModalViewController -> gate the INCLUDE_CAMERA_USAGE body off on watch (camera capture is a no-op there). - sendLocalNotification: UNNotificationSound soundNamed: -> use defaultSound on watch. - UNAuthorizationStatusEphemeral -> guard the iOS-14 ephemeral check off on watch. Watch target now builds clean against the full CI hint set (verified locally). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): disable INCLUDE_CN1_CAMERA on watchOS (AVFoundation capture absent) The AVFoundation camera stack (CN1Camera.{h,m} + the IOSNative camera natives) uses AVCaptureSession/AVCaptureConnection/AVCaptureFileOutput etc., none of which exist on watchOS. IPhoneBuilder uncomments //#define INCLUDE_CN1_CAMERA in this central header (included first by every camera TU); undo it on the watch slice so the whole camera path compiles out consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): generate the FQN-mangled Stub.main symbol in the bootstrap cn1_watch_app_main called <Main>Stub_main..., but request.getMainClass() is the simple class name while the generated Stub's C symbol is mangled from the fully-qualified <package>.<Main>Stub. Use request.getPackageName() to build the FQN so the watch target links (it was the sole undefined symbol: com_codenameone_examples_hellocodenameone_HelloCodenameOneStub_main...). The watch target now compiles and links on CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): target the -Dmain rename at the FQN-mangled Stub source The duplicate _main (phone Stub int main vs SwiftUI @main) persisted because the per-file -Dmain rename matched mangle(mainClass)+"Stub.m" (simple name) while the generated source is the FQN-mangled com_<pkg>_<Main>Stub.m. Reuse mainStub (now FQN-based) so the rename lands and the watch target links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): compile error - mainStub was out of scope in applyXcodeSettings The previous commit referenced mainStub (a local of writeWatchEntry) inside applyXcodeSettings, so the plugin failed to compile and every job that builds it went red. Recompute the FQN-mangled phone Stub source name locally in applyXcodeSettings. Verified the plugin module compiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(watch): pin the 15 mutable-image/chart goldens to CI's watch rendering build-ios-watch passed (160 screenshots, 145 matched); the 15 image-variant graphics tests + chart-time render with slightly different anti-aliasing on the CI watch runtime than on my local capture (same 416x496 size, correct shapes - verified). Update those goldens to CI's actual output so the steady-state run is 0 updates / fully green with goldens that match the validation environment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): apply mutable-image transform on watchOS; remove SE skin process - nativeSetTransformMutableImpl: the code that actually set the mutable transform (currentMutableTransformSet=YES) was wrapped in #if !TARGET_OS_WATCH, so rotate/translate/perspective/camera did nothing on the "image" graphics variants. Add a watch branch building the 2D affine directly. - Remove the JavaSE watch-simulator-skin machinery (maven/javase/pom.xml skin generation, tools/watch-skins/, JavaSEPort skin loading) - superseded by the native iOS watch screenshot pipeline. JavaSEPort.isWatch() now defaults false, which is correct: only the native watch build splits the graphics grid. - WATCHOS_PORT.md: correct the bootstrap description - the watch boots the regular main class's Stub (full app code), it is NOT tree-shaken to watchMain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): bundle the app's runtime resources into the watch target The watch target shipped with an EMPTY resources phase, so at runtime the CN1 watch app couldn't find the native theme (Resources.open("/iOS7Theme.res")), the app theme.res/CN1Resource.res, material-design-font.ttf (FontImage glyphs), or any bundled image -> it fell back to the default look (gray 3D buttons, gray switches, red badges instead of chevrons) and images failed (black boxes). Mirror the iOS app target's bundle resources into the watch target, skipping iOS-only UI/icon assets (.xcassets AppIcon has no watch-applicable content; .storyboard/.xib are iOS UI). Verified: kotlin.png now renders the flat iOS native theme (blue button, green switch) matching the iOS golden. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch): render mutable-image text + a real gaussian blur; regen goldens - nativeDrawStringMutableImpl drew text via the instance method, which on watch enqueues a SCREEN op instead of drawing into the mutable image's UIGraphics context -> the text never landed in the image (black box / "only top line"). Draw the string straight into the active context on watch. - gausianBlurImage returned the input unchanged on watch (no CIFilter). Implement a 3-pass vImage box convolve (Accelerate) so graphics-gaussian-blur actually blurs. - Regenerate scripts/ios/screenshots-watch from the corrected renderer: the old goldens captured the buggy output (default theme, no transforms, black text, no blur). Verified locally: kotlin.png = flat iOS native theme, rotate/translate image variants transform, draw-string-image renders multi-line, gaussian-blur blurs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(wear): Android Wear OS build support + developer guide wearables chapter Adds first-class Wear OS support to the Android build: the new android.wear hint marks the build as a Wear OS app by injecting the watch hardware feature (android.hardware.type.watch, required) into the manifest, declaring the app standalone by default (com.google.android.wearable.standalone, opt out via android.wear.standalone=false), and raising the minimum SDK to the Wear OS 2.0 baseline (API 23). The Codename One Android port renders the UI through its normal pipeline on the watch, and CN.isWatch() already reports Wear OS via PackageManager.FEATURE_WATCH. With the hint off the manifest is unchanged. Adds a Wearables chapter to the developer guide covering both wearable paths: the native Apple Watch (watchOS) Core Graphics port (watchNative.* hints, companion vs standalone, supported/unsupported APIs) and the Android Wear OS build (android.wear), plus CN.isWatch() detection and watch UI design guidance. The matching BuildDaemon changes (cloud builds) are made in the BuildDaemon repo: a ported WatchNativeBuilder and the same android.wear manifest logic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch-ci): wait for full screenshot set + replace broken mutable-image goldens Two fixes to get build-ios-watch green, found by inspecting the failed run's watch-ui-tests artifact (15 different, 43 missing of ~203): 1. Premature-settling race in run-watch-ui-tests.sh. The capture loop declared the suite done after the streamed PNG count was merely stable for 24s, then compared. The watch streams in bursts with multi-second GC/render pauses between phases (the theme tests arrive in a late burst), so the loop settled on the partial set and the late screenshots -- received by the WS sink (logged status=ok) but written to disk after the comparison snapshot -- showed up as false "missing". Now wait until the full golden count has streamed (preferred), with a generous idle-plateau fallback only when fewer are produced. This was the cause of all 43 "missing". 2. The 15 "different" were all the mutable-image-backed graphics variants (fill/draw/stroke-*-image, transform-camera/perspective-image, chart-time). Inspecting the artifact showed the CI output renders these correctly (filled triangles/polygons/strokes) while the stored goldens were blank/unfilled -- the goldens had been regenerated locally against a stale core that did not render the mutable-image path. Replaced those 15 goldens with the verified- correct CI output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(docs,spotbugs): pass developer-guide quality gate + drop unbox/rebox - Wearables.asciidoc: fix the 10 Vale findings (contractions isn't/it's/don't/ aren't/they're, drop "very", de-hyphenate "auto-enables") and the paragraph- capitalization finding (a paragraph that began with lowercase "and" after a source block). Verified locally with vale + the paragraph-capitalization ruby check (both clean) so the "developer guide quality issues" gate passes. - JavascriptMethodGenerator: drop the (int) casts on the two Integer-keyed startToBlock lookups SpotBugs flagged (BX_UNBOXING_IMMEDIATELY_REBOXED at the Jump-target and switch-default lookups) -- the map is Map<Integer,Integer> so passing the Integer directly avoids the unbox-then-rebox. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(watch-ci): make the screenshot gate strict + sync goldens The watch gate was lenient: it grepped compare.json for status "mismatch" (the actual status is "different") so visual diffs never failed the build, and it only counted "missing_actual" -- so a screenshot that streamed with no golden ("missing_expected") was ignored too. The job therefore went green while the PR comment still listed 7 different + 6 missing screenshots. Gate now mirrors the iOS jobs: propagate cn1ss_process_and_report's central verdict (15 = a screenshot differs/errors vs its golden; 17 = fewer produced than there are goldens) under CN1SS_FAIL_ON_MISMATCH=1, and additionally fail on "missing_expected" so a new test cannot land without shipping its golden. The captured-nothing guard now uses the actual entry count. Synced the goldens to the watch output so the suite is in sync (209 = 203 + 6): - updated the 7 "different" goldens (LightweightPickerButtons x4, Sheet slide-up, TabsTheme dark/light) -- their stored goldens predated the current renderer; - added 6 goldens that had no reference yet (css-gradients, landscape, Lottie/SVG animated, SVGStatic, PaletteOverrideTheme_dark). Verified locally with cn1ss_process_and_report against the synced goldens: 209 equal, 0 different, 0 missing. The goldens need not be pretty (the watch form factor differs and some tests are not meaningful there) -- they only need to run and stay in sync, which the strict gate now enforces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(wear): list wear hints in the build-hints table, clarify scope, add sim shot Addresses developer-guide gaps for the wearable support: - Build-hints table (BuildHintSchemaDefaults, which feeds the Control Center): add the Android Wear category (android.wear, android.wear.standalone) and the two watchNative hints that were only in the guide (watchNative.displayName, watchNative.embedCompanion). Also corrected the watchNative.minDeploymentTarget default text (9.0 -> 10.0) to match the builder. - Clarify that codename1.watchMain / watchNative.enabled enable ONLY the Apple Watch (watchOS) build and never produce a Wear OS build implicitly -- Wear OS is enabled explicitly with android.wear=true; both can be set to target both. - Add an Apple Watch simulator screenshot (real Core Graphics-rendered CN1 UI in a watch device frame) to the Apple Watch section. Verified locally: vale + paragraph-capitalization + asciidoctor all clean on the chapter, the image resolves and is referenced (unused-image gate), schema compiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(docs): img/ prefix on the watch screenshot so the website publishes it The Hugo website republishes the developer guide and copies its img/ tree to public/developer-guide/img/. The screenshot was referenced as image::wearables/... (no img/ prefix), so the published HTML pointed at public/developer-guide/wearables/... which doesn't exist and the website link/image validator failed. Match the existing convention (image::img/<dir>/...). Asciidoctor HTML output doesn't check src existence so this only surfaced in the website validator; the file already lives at docs/developer-guide/img/wearables/apple-watch-simulator.png. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6ff3150 commit 930f8d6

276 files changed

Lines changed: 4111 additions & 147 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/scripts-ios.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
- 'scripts/build-ios-port.sh'
1010
- 'scripts/build-ios-app.sh'
1111
- 'scripts/run-ios-ui-tests.sh'
12+
- 'scripts/run-watch-ui-tests.sh'
1213
- 'scripts/run-ios-native-tests.sh'
1314
- 'scripts/ios/notification-tests/native-tests/**'
1415
- 'scripts/ios/notification-tests/install-native-notification-tests.sh'
@@ -17,6 +18,7 @@ on:
1718
- 'scripts/ios/tests/**'
1819
- 'scripts/ios/screenshots/**'
1920
- 'scripts/ios/screenshots-metal/**'
21+
- 'scripts/ios/screenshots-watch/**'
2022
- 'scripts/templates/**'
2123
- '!scripts/templates/**/*.md'
2224
- 'CodenameOne/src/**'
@@ -41,6 +43,7 @@ on:
4143
- 'scripts/build-ios-port.sh'
4244
- 'scripts/build-ios-app.sh'
4345
- 'scripts/run-ios-ui-tests.sh'
46+
- 'scripts/run-watch-ui-tests.sh'
4447
- 'scripts/run-ios-native-tests.sh'
4548
- 'scripts/ios/notification-tests/native-tests/**'
4649
- 'scripts/ios/notification-tests/install-native-notification-tests.sh'
@@ -49,6 +52,7 @@ on:
4952
- 'scripts/ios/tests/**'
5053
- 'scripts/ios/screenshots/**'
5154
- 'scripts/ios/screenshots-metal/**'
55+
- 'scripts/ios/screenshots-watch/**'
5256
- 'scripts/templates/**'
5357
- '!scripts/templates/**/*.md'
5458
- 'CodenameOne/src/**'
@@ -506,3 +510,146 @@ jobs:
506510
path: artifacts
507511
if-no-files-found: warn
508512
retention-days: 14
513+
514+
build-ios-watch:
515+
# Native Apple Watch (watchOS) screenshot pipeline. The watch slice
516+
# auto-enables from codename1.watchMain in the sample, so build-ios-app.sh
517+
# generates the <Main>Watch target alongside the iOS app. This job renders
518+
# the cn1ss suite on the watch simulator through the Core Graphics backend
519+
# (no GL/Metal on watchOS), streams frames to the same Cn1ssScreenshotServer
520+
# WS sink the iOS jobs use, and compares against scripts/ios/screenshots-watch.
521+
# Isolated in its own job (like build-ios-metal) so the watch result is a
522+
# distinct check and a regression there doesn't mask the iOS path.
523+
needs: build-port
524+
permissions:
525+
contents: read
526+
pull-requests: write
527+
issues: write
528+
runs-on: macos-15
529+
timeout-minutes: 60
530+
concurrency:
531+
group: mac-ci-${{ github.workflow }}-watch-${{ github.ref_name }}
532+
cancel-in-progress: true
533+
534+
env:
535+
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
536+
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
537+
538+
steps:
539+
- uses: actions/checkout@v6
540+
541+
- name: Cache CocoaPods and user gems
542+
uses: actions/cache@v5
543+
with:
544+
path: |
545+
~/.gem
546+
~/Library/Caches/CocoaPods
547+
~/.cocoapods/repos
548+
key: ${{ runner.os }}-pods-v1-${{ hashFiles('scripts/setup-workspace.sh') }}
549+
restore-keys: |
550+
${{ runner.os }}-pods-v1-
551+
552+
- name: Ensure CocoaPods tooling
553+
run: |
554+
mkdir -p ~/.codenameone
555+
cp maven/UpdateCodenameOne.jar ~/.codenameone/
556+
set -euo pipefail
557+
if ! command -v ruby >/dev/null; then
558+
echo "ruby not found"; exit 1
559+
fi
560+
GEM_USER_DIR="$(ruby -e 'print Gem.user_dir')"
561+
export PATH="$GEM_USER_DIR/bin:$PATH"
562+
if ! command -v pod >/dev/null 2>&1; then
563+
gem install cocoapods xcodeproj --no-document --user-install
564+
fi
565+
pod --version
566+
567+
- name: Compute setup-workspace hash
568+
id: setup_hash
569+
run: |
570+
set -euo pipefail
571+
echo "hash=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')" >> "$GITHUB_OUTPUT"
572+
573+
- name: Set TMPDIR
574+
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
575+
576+
- name: Cache codenameone-tools
577+
uses: actions/cache@v5
578+
with:
579+
path: ${{ runner.temp }}/codenameone-tools
580+
key: ${{ runner.os }}-cn1-tools-${{ steps.setup_hash.outputs.hash }}
581+
restore-keys: |
582+
${{ runner.os }}-cn1-tools-
583+
584+
- name: Cache Maven repository
585+
uses: actions/cache@v5
586+
with:
587+
path: ~/.m2/repository
588+
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
589+
restore-keys: |
590+
${{ runner.os }}-m2-
591+
592+
- name: Restore cn1-binaries cache
593+
uses: actions/cache@v5
594+
with:
595+
path: ../cn1-binaries
596+
key: cn1-binaries-${{ runner.os }}-${{ steps.setup_hash.outputs.hash }}
597+
restore-keys: |
598+
cn1-binaries-${{ runner.os }}-
599+
600+
- name: Restore built CN1 + iOS port artifacts
601+
uses: actions/cache/restore@v4
602+
with:
603+
path: |
604+
~/.m2/repository/com/codenameone
605+
Themes
606+
Ports/iOSPort/nativeSources
607+
key: ${{ needs.build-port.outputs.cn1_built_cache_key }}
608+
fail-on-cache-miss: true
609+
610+
- name: Build sample iOS app (generates the watch target)
611+
id: build-ios-app
612+
run: ./scripts/build-ios-app.sh -q -DskipTests
613+
timeout-minutes: 30
614+
615+
- name: Ensure watchOS simulator runtime
616+
run: |
617+
set -euo pipefail
618+
if ! xcrun simctl list runtimes available 2>/dev/null | grep -qi watchOS; then
619+
echo "No watchOS runtime found; attempting download"
620+
xcodebuild -downloadPlatform watchOS || true
621+
fi
622+
xcrun simctl list runtimes available 2>/dev/null | grep -i watchOS || true
623+
xcrun simctl list devices available 2>/dev/null | grep -i "Apple Watch" || true
624+
625+
- name: Run watchOS UI screenshot tests
626+
env:
627+
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/watch-ui-tests
628+
SCREENSHOT_REF_DIR: ${{ github.workspace }}/scripts/ios/screenshots-watch
629+
CN1SS_COMMENT_MARKER: '<!-- CN1SS_IOS_WATCH_COMMENT -->'
630+
CN1SS_PREVIEW_SUBDIR: ios-watch
631+
CN1SS_REPORT_TITLE: 'Apple Watch (watchOS) screenshot updates'
632+
CN1SS_SUCCESS_MESSAGE: '✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.'
633+
CN1SS_COMMENT_LOG_PREFIX: '[run-watch-ui-tests]'
634+
CN1SS_FAIL_ON_MISMATCH: '1'
635+
# The watch form factor splits the iOS 2x2 graphics grid into separate
636+
# full-screen captures; tolerate a small tail drop, matching the GL job.
637+
CN1SS_ALLOWED_MISSING: '4'
638+
run: |
639+
set -euo pipefail
640+
mkdir -p "${ARTIFACTS_DIR}"
641+
echo "workspace='${{ steps.build-ios-app.outputs.workspace }}'"
642+
echo "scheme='${{ steps.build-ios-app.outputs.scheme }}'"
643+
./scripts/run-watch-ui-tests.sh \
644+
"${{ steps.build-ios-app.outputs.workspace }}" \
645+
"${{ steps.build-ios-app.outputs.scheme }}"
646+
timeout-minutes: 45
647+
648+
- name: Upload watch artifacts
649+
if: always()
650+
uses: actions/upload-artifact@v7
651+
with:
652+
name: watch-ui-tests
653+
path: artifacts
654+
if-no-files-found: warn
655+
retention-days: 14

CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5601,6 +5601,17 @@ public boolean isDesktop() {
56015601
return false;
56025602
}
56035603

5604+
/// Indicates whether the application is running on a smartwatch form factor
5605+
/// (Apple Watch / Wear OS). Notice that this is often a guess derived from
5606+
/// the device/skin metadata.
5607+
///
5608+
/// #### Returns
5609+
///
5610+
/// true if the device is assumed to be a smartwatch
5611+
public boolean isWatch() {
5612+
return false;
5613+
}
5614+
56045615
/// Returns true if the device has dialing capabilities
56055616
///
56065617
/// #### Returns

CodenameOne/src/com/codename1/ui/CN.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,17 @@ public static boolean isDesktop() {
891891
return Display.impl.isDesktop();
892892
}
893893

894+
/// Indicates whether the application is running on a smartwatch form factor
895+
/// (Apple Watch / Wear OS). Notice that this is often a guess derived from
896+
/// the device metadata.
897+
///
898+
/// #### Returns
899+
///
900+
/// true if the device is assumed to be a smartwatch
901+
public static boolean isWatch() {
902+
return Display.impl.isWatch();
903+
}
904+
894905
/// Returns the size of the desktop hosting the application window when running on a desktop platform.
895906
///
896907
/// #### Returns

CodenameOne/src/com/codename1/ui/Display.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4343,6 +4343,17 @@ public boolean isDesktop() {
43434343
return impl.isDesktop();
43444344
}
43454345

4346+
/// Indicates whether the application is running on a smartwatch form factor
4347+
/// (Apple Watch / Wear OS). Notice that this is often a guess derived from
4348+
/// the device metadata.
4349+
///
4350+
/// #### Returns
4351+
///
4352+
/// true if the device is assumed to be a smartwatch
4353+
public boolean isWatch() {
4354+
return impl.isWatch();
4355+
}
4356+
43464357
/// Returns true if the device has dialing capabilities
43474358
///
43484359
/// #### Returns

Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5670,6 +5670,20 @@ public boolean isTablet() {
56705670
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
56715671
}
56725672

5673+
private Boolean watchCache;
5674+
5675+
@Override
5676+
public boolean isWatch() {
5677+
if(watchCache == null) {
5678+
// PackageManager.FEATURE_WATCH ("android.hardware.type.watch") is
5679+
// the canonical Wear OS marker; use the string literal so this
5680+
// compiles regardless of the configured minimum SDK level.
5681+
watchCache = getContext().getPackageManager()
5682+
.hasSystemFeature("android.hardware.type.watch");
5683+
}
5684+
return watchCache;
5685+
}
5686+
56735687
/**
56745688
* Executes r on the UI thread and blocks the EDT to completion
56755689
* @param r runnable to execute
@@ -8228,6 +8242,9 @@ public String getPlatformName() {
82288242
* @inheritDoc
82298243
*/
82308244
public String[] getPlatformOverrides() {
8245+
if (isWatch()) {
8246+
return new String[]{"watch", "android", "android-watch"};
8247+
}
82318248
if (isTablet()) {
82328249
return new String[]{"tablet", "android", "android-tab"};
82338250
} else {

Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,106 @@ static void register() {
9494
+ "hololight = Android Holo Light (API 14+). legacy = pre-Holo "
9595
+ "Android theme. (Deprecated alias: cn1.androidTheme; "
9696
+ "and.hololight=true is also accepted for back-compat.)");
97+
98+
// watchOS native build (Apple Watch). Adds a watchOS app target to the
99+
// iOS Xcode project, rendering the CN1 UI via the Core Graphics backend.
100+
set("{{@watchNative}}.label", "Apple Watch (watchOS)");
101+
set("{{@watchNative}}.description",
102+
"Builds an Apple Watch app from the same project, rendering the "
103+
+ "Codename One UI on watchOS via the Core Graphics backend. The "
104+
+ "watch app is a separate arm64_32 target; in the default "
105+
+ "companion mode it is embedded in the iOS .ipa and installs "
106+
+ "with the phone app.");
107+
108+
set("{{#watchNative#watchNative.enabled}}.label", "Enable watchOS target");
109+
set("{{#watchNative#watchNative.enabled}}.type", "Select");
110+
set("{{#watchNative#watchNative.enabled}}.values", "false,true");
111+
set("{{#watchNative#watchNative.enabled}}.description",
112+
"When true, adds an Apple Watch app target to the generated "
113+
+ "Xcode project. Also auto-enabled whenever codename1.watchMain "
114+
+ "is declared next to codename1.mainName in "
115+
+ "codenameone_settings.properties, so the double app is produced "
116+
+ "as part of the regular iPhone build. Requires the Ruby "
117+
+ "xcodeproj gem (bundled with CocoaPods).");
118+
119+
set("{{#watchNative#watchNative.mainClass}}.label", "Watch lifecycle class");
120+
set("{{#watchNative#watchNative.mainClass}}.type", "String");
121+
set("{{#watchNative#watchNative.mainClass}}.description",
122+
"Fully-qualified watch entry/lifecycle class. Normally set via "
123+
+ "codename1.watchMain; this hint is an override. May equal the "
124+
+ "phone main class - a distinct class lets the watch slice "
125+
+ "tree-shake from its own root. Defaults to the phone main class "
126+
+ "when watchNative.enabled=true without a watch entry.");
127+
128+
set("{{#watchNative#watchNative.distribution}}.label", "Distribution");
129+
set("{{#watchNative#watchNative.distribution}}.type", "Select");
130+
set("{{#watchNative#watchNative.distribution}}.values", "companion,standalone");
131+
set("{{#watchNative#watchNative.distribution}}.description",
132+
"companion = the watch app is embedded in the iOS app and "
133+
+ "installs with it (WKCompanionAppBundleIdentifier pinned to "
134+
+ "the iOS bundle). standalone = an independent watch-only app.");
135+
136+
set("{{#watchNative#watchNative.bundleId}}.label", "Watch bundle identifier");
137+
set("{{#watchNative#watchNative.bundleId}}.type", "String");
138+
set("{{#watchNative#watchNative.bundleId}}.description",
139+
"Bundle id of the watch app. Defaults to <package>.watchkitapp.");
140+
141+
set("{{#watchNative#watchNative.minDeploymentTarget}}.label", "Minimum watchOS version");
142+
set("{{#watchNative#watchNative.minDeploymentTarget}}.type", "String");
143+
set("{{#watchNative#watchNative.minDeploymentTarget}}.description",
144+
"WATCHOS_DEPLOYMENT_TARGET for the watch target. Defaults to 10.0 "
145+
+ "(single-target WKApplication apps + WidgetKit complications).");
146+
147+
set("{{#watchNative#watchNative.teamId}}.label", "Apple team id");
148+
set("{{#watchNative#watchNative.teamId}}.type", "String");
149+
set("{{#watchNative#watchNative.teamId}}.description",
150+
"Development team for signing the watch target. Defaults to the "
151+
+ "iOS team id (ios.teamId / ios.release.teamId).");
152+
153+
set("{{#watchNative#watchNative.displayName}}.label", "Watch app name");
154+
set("{{#watchNative#watchNative.displayName}}.type", "String");
155+
set("{{#watchNative#watchNative.displayName}}.description",
156+
"Name shown under the watch app icon. Defaults to the app display "
157+
+ "name (codename1.displayName), then the main class name.");
158+
159+
set("{{#watchNative#watchNative.embedCompanion}}.label", "Embed in iOS app");
160+
set("{{#watchNative#watchNative.embedCompanion}}.type", "Select");
161+
set("{{#watchNative#watchNative.embedCompanion}}.values", "false,true");
162+
set("{{#watchNative#watchNative.embedCompanion}}.description",
163+
"When true (companion distribution), adds the watch app as a build "
164+
+ "dependency of the iOS app so the pair archives together. Off by "
165+
+ "default so the iOS build is unaffected; enable it for a packaged "
166+
+ "companion submission.");
167+
168+
// Wear OS native build (Android). A Wear OS app is a regular Android app
169+
// that declares the watch hardware feature; the CN1 UI renders through
170+
// the normal Android pipeline (no separate backend, unlike watchOS).
171+
set("{{@androidWear}}.label", "Wear OS (Android)");
172+
set("{{@androidWear}}.description",
173+
"Builds the Android app as a Wear OS app: declares the watch "
174+
+ "hardware feature, marks the app standalone (runs without a "
175+
+ "paired phone app) and raises the minimum SDK to the Wear OS 2.0 "
176+
+ "baseline (API 23). CN.isWatch() returns true at runtime via "
177+
+ "PackageManager.FEATURE_WATCH. Independent of the Apple Watch "
178+
+ "build; enable both to target both wearables.");
179+
180+
set("{{#androidWear#android.wear}}.label", "Enable Wear OS build");
181+
set("{{#androidWear#android.wear}}.type", "Select");
182+
set("{{#androidWear#android.wear}}.values", "false,true");
183+
set("{{#androidWear#android.wear}}.description",
184+
"When true, marks the Android build as a Wear OS app (manifest "
185+
+ "uses-feature android.hardware.type.watch, standalone meta-data, "
186+
+ "minimum SDK floor API 23). With the hint off the manifest is "
187+
+ "unchanged.");
188+
189+
set("{{#androidWear#android.wear.standalone}}.label", "Standalone Wear app");
190+
set("{{#androidWear#android.wear.standalone}}.type", "Select");
191+
set("{{#androidWear#android.wear.standalone}}.values", "true,false");
192+
set("{{#androidWear#android.wear.standalone}}.description",
193+
"Declares the Wear app standalone (com.google.android.wearable."
194+
+ "standalone), so it installs and runs directly on the watch "
195+
+ "without a companion phone app. Defaults to true. Only applies "
196+
+ "when android.wear=true.");
97197
}
98198

99199
/** Idempotent setter: does not overwrite user / project-level hint metadata. */

Ports/iOSPort/nativeSources/AudioPlayer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@
2323
#import <Foundation/Foundation.h>
2424
#import <UIKit/UIKit.h>
2525
#import <AVFoundation/AVFoundation.h>
26+
#include "TargetConditionals.h"
27+
#if !TARGET_OS_WATCH
28+
// AudioToolbox is unavailable on watchOS; AudioPlayer uses AVAudioPlayer
29+
// (AVFoundation, present on watch) and doesn't need AudioToolbox types here.
2630
#import <AudioToolbox/AudioToolbox.h>
31+
#endif
2732

2833
@interface AudioPlayer : NSObject<AVAudioPlayerDelegate> {
2934
AVAudioPlayer* playerInstance;

Ports/iOSPort/nativeSources/AudioPlayer.m

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,11 @@ - (void)playAudio {
167167
if (!success) {
168168
CN1Log(@"ERROR");
169169
}
170+
#if !TARGET_OS_WATCH
171+
// UIApplication is unavailable on watchOS.
170172
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
171-
173+
#endif
174+
172175
currentlyPlaying = self;
173176
if(playerInstance != nil) {
174177
/*#ifndef CN1_USE_ARC

0 commit comments

Comments
 (0)