Apple TV (tvOS) and Google TV (Android TV) support#5261
Conversation
Phase 1 — shared form-factor API + @media: - CN.isTV() / Display.isTV() / CodenameOneImplementation.isTV() (mirrors isWatch()) - iOS isTV() via new isRunningOnTV() native (TARGET_OS_TV) + {tv,ios,appletv} overrides - Android isTV() (television/leanback feature + UI-mode) + {tv,android,android-tv} overrides - Resources.loadTheme selects device-tv / device-watch @media variants at runtime (also completes @media for the existing watch port, which never wired it) - CSSDeviceFormFactorMediaQueryTest (runs green) + dev-guide css.asciidoc Phase 2 — Google TV / Android TV: - AndroidGradleBuilder: LEANBACK_LAUNCHER category, android.software.leanback + touchscreen-optional uses-feature, generated 320x180 tv_banner; gated on android.tv - build-hint schema (android.tv / android.tv.banner); README + TVPlatforms dev guide Phase 3 — Apple TV (tvOS): - TvNativeBuilder: adds a separate appletvos Xcode target (family 3, Metal, GL-only sources excluded), modeled on the Mac Catalyst slice; wired into IPhoneBuilder + CN1BuildMojo (codename1.tvMain) + build-hint schema. Verified end-to-end locally: generates the <Main>TV target against the tvOS 26 SDK. - Native tvOS slice (#if !TARGET_OS_TV) in progress: MessageUI + several UIKit conformance guards done; remaining guards tracked in TVOS_PORT.md. Phase 4 — tvOS screenshot tests: - scripts/run-tv-ui-tests.sh + a non-blocking build-ios-tv CI job (continue-on-error until the native slice compiles and goldens are seeded, as the watch port was); scripts/ios/screenshots-tv goldens dir; sample auto-enables via codename1.tvMain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
|
Compared 11 screenshots: 11 matched. |
|
Compared 122 screenshots: 122 matched. |
|
Compared 129 screenshots: 129 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 126 screenshots: 126 matched. Benchmark ResultsDetailed Performance Metrics
|
Cloudflare Preview
|
|
Compared 128 screenshots: 128 matched. |
|
Compared 128 screenshots: 128 matched. |
|
Compared 129 screenshots: 129 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 210 screenshots: 210 matched. |
|
Compared 129 screenshots: 129 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Brings the HelloCodenameOneTV target much closer to compiling against the tvOS 26 simulator SDK (verified locally). Guarded/handled: - IOSNative.m: broadened the watch guards to also cover tvOS for the native features tvOS lacks (CLLocation region monitoring, MPMoviePlayer, UIPasteboard, orientation, status bar, telephony) — 144 errors -> 0. (Compile-first measure; re-enabling tvOS-capable features like UITextField/audio is a follow-up, see TVOS_PORT.md.) - CodenameOne_GLAppDelegate.m: guard UNNotificationResponse / UNTextInputNotificationResponse and the legacy openURL delegate (tvOS-absent); hold currentNotificationResponse as id. - NetworkConnectionImpl.m: guard setNetworkActivityIndicatorVisible (tvOS-removed). - UIWebViewEventDelegate.[hm]: drop the legacy UIWebView browser peer on tvOS. - DrawStringTextureCache.m: use sizeWithAttributes: on tvOS (sizeWithFont: removed). - CodenameOne_GLViewController.m: tvOS trusts view bounds for orientation (like Mac Catalyst). - sample LocalNotificationNativeImpl.m: no-op the delivered-notification calls on tvOS. TVOS_PORT.md documents the remaining CodenameOne_GLViewController.m surgical guards (~63 sites incl. the UIPopoverController->id change) and the exact local build recipe (arm64, preserve the CN1_USE_METAL define, -ferror-limit=0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CodenameOne_GLViewController.m now COMPILES for tvOS: ~63 surgical #if !TARGET_OS_TV guards (device orientation, status bar, UIToolbar keyboard input-accessory, on-screen keyboard notifications, UIHoverGestureRecognizer, the UIImagePicker/UIDatePicker/UIPickerView/UIActionSheet/UIDocumentInteraction delegate methods), legacy sizeWithFont:/drawAtPoint:withFont: -> the modern sizeWithAttributes:/withAttributes: API, and UIPopoverController -> id. - IOSNative.m: reverted the earlier blanket watch-guard broadening (it was wrong: tvOS supports most of what watchOS lacks - CIFilter, vImage, UIView capture, audio, UNUserNotificationCenter). Correct model is tvOS≈iOS, guarding only the genuinely tvOS-absent APIs. Remaining IOSNative.m surgical work is ~6 localized features (UIWebView, MPMoviePlayer*, UIPasteboard, orientation, UIDocumentInteractionController, scrollsToTop) + LAContext + push actions, documented in TVOS_PORT.md. Both files are structurally valid (balanced #if/#endif) and compile unchanged for iOS. Verified against the tvOS 26 simulator SDK locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression fixes (my tvOS changes broke the existing iOS/Mac builds): - UIPopoverController -> id was applied unconditionally, breaking iOS/Mac where popoverController.delegate needs the real type. Now conditional: id only on tvOS (where UIPopoverController is unavailable), UIPopoverController* elsewhere (IOSNative.m + CodenameOne_GLViewController.m). - sizeWithFont:/drawAtPoint:withFont: were modernized to the attributed-string API on ALL platforms, changing existing iOS rendering. Restored: iOS keeps the original sizeWithFont:/withFont:; only the tvOS slice (where they were removed) uses sizeWithAttributes:/withAttributes:. iOS target verified building clean against the iphonesimulator SDK; tvOS GLViewController.m still compiles; the iOS API paths are byte-for-byte unchanged. Developer-guide prose gate: - TVPlatforms.asciidoc: 320x180 -> 320×180 (Vale proselint typography). - Add "Leanback" (Android's android.software.leanback / LEANBACK_LAUNCHER proper noun) to languagetool-accept.txt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s target compiles + links Guards UIWebView, MPMoviePlayer/AVKit video peer, UIPasteboard, pickers/ date-picker/action-sheet/activity/print controllers, device orientation, MessageUI, LocalAuthentication biometrics, CLLocationManager tvOS-absent properties, and the UNNotification action/category registration with #if !TARGET_OS_TV (broadening existing watch guards where present). The iOS #else path is byte-identical (verified: iphonesimulator BUILD SUCCEEDED); appletvsimulator now BUILD SUCCEEDED with 0 errors and links. Plain local notifications via UNUserNotificationCenter stay enabled on tvOS; only the action/category/attachment/sound extras are guarded out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two failures surfaced only in the CI tvOS build (my local loop was arm64 with ENABLE_WKWEBVIEW undefined, which masked both): 1. IOSNative.m imported <WebKit/WebKit.h> under the WKWebView path (ENABLE_WKWEBVIEW, which the builder defines). tvOS ships neither UIWebView nor WKWebView, so guard the import + supportsWKWebKit with !TARGET_OS_TV (inert on iOS/watch). 2. run-tv-ui-tests.sh built the tvOS target with ONLY_ACTIVE_ARCH=YES and no explicit arch; a destination-less appletvsimulator build resolved the active arch to x86_64, which (a) can't compile the NEON-only IOSSimd.m and (b) would not launch on the arm64 tvOS simulator of the macos-15 runner. Pin ARCHS=arm64 ONLY_ACTIVE_ARCH=NO to match the runner + simulator. Verified: appletvsimulator arm64 build with ENABLE_WKWEBVIEW BUILD SUCCEEDED, 0 errors, links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The captureCamera native (UIImagePickerController + UIPopoverController + presentModalViewController) is gated behind INCLUDE_CAMERA_USAGE, which the builder only #defines when the app declares NSCameraUsageDescription. The hellocodenameone CI sample does, so this block compiles on CI (my earlier local loop had the define off and skipped it). tvOS has no camera, so broaden the guard to !TARGET_OS_WATCH && !TARGET_OS_TV -- a no-op there, exactly like watch. Verified with the CI define set (ENABLE_WKWEBVIEW + INCLUDE_CAMERA_USAGE): appletvsimulator arm64 and iphonesimulator both BUILD SUCCEEDED, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bsent) UNNotificationContent.userInfo is unavailable on tvOS. The foreground presentation delegate (userNotificationCenter:willPresentNotification:) reads userInfo to route local/push payloads, so guard it with !TARGET_OS_TV -- it is an optional UNUserNotificationCenterDelegate method, mirroring the already- guarded didReceiveNotificationResponse: just below it. This block compiles on CI because the builder uncomments CN1_INCLUDE_NOTIFICATIONS (the sample uses local notifications); my earlier local loop had it off. Verified against CI's full define set (ENABLE_WKWEBVIEW + INCLUDE_CAMERA_USAGE + CN1_INCLUDE_NOTIFICATIONS(2) + INCLUDE_CN1_PUSH(2)): appletvsimulator arm64 and iphonesimulator both BUILD SUCCEEDED + link, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CN1Camera.m is the native Camera API (AVCaptureDevice video, UIImagePicker, AVCaptureVideoOrientation) gated by INCLUDE_CN1_CAMERA, which the builder defines when the app declares camera usage. tvOS has no camera, so broaden all 14 INCLUDE_CN1_CAMERA gates to '&& !TARGET_OS_TV' -- the file already has #else stubs (return 0/JAVA_NULL) for the camera-disabled case, so tvOS uses those and the class implementation is omitted. Inert on iOS/watch. This file is newer on master (#5177) than my stale local generated project, so it only surfaced once I synced all 126 nativeSources. Verified: appletvsimulator arm64 (CN1_USE_METAL on) and iphonesimulator both BUILD SUCCEEDED + link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the zero-screenshot run: the tvOS app crashed at launch with 'Could not load NIB in bundle ... CodenameOne_GLViewController'. The app instantiates the view controller via initWithNibName:@"CodenameOne_GLViewController", but TvNativeBuilder excludes the iOS XIBs from the tvOS bundle. Mac Catalyst already passes nil there (Metal layer is attached programmatically); extend that to tvOS (TARGET_OS_MACCATALYST || TARGET_OS_TV). Verified on the Apple TV 4K simulator: the app now boots and runs the cn1ss suite end-to-end (CN1SS:INFO: suite starting/finished, png_bytes>0 per test). Inert on iOS (build verified). Also harden run-tv-ui-tests.sh: capture the app console (--console-pty) and any crash report into the artifacts, and break the wait loop early on CN1SS:SUITE:FINISHED or a detected crash instead of always blocking MAX_WAIT -- so a future no-screenshot run is diagnosable instead of silent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…screenTexture readback)
Two Catalyst-only guards left tvOS with a blank screenshot pipeline:
1. CodenameOne_GLViewController -loadView (which builds the METALView
programmatically and sets it as the controller's view for the nil-NIB path)
was #if defined(CN1_USE_METAL) && TARGET_OS_MACCATALYST. tvOS also uses the
nil-NIB path (its XIBs are excluded from the bundle), so without loadView the
default UIViewController handed back a plain UIView, no METALView was ever
created ('EAGLView not found'), and forms rendered to nothing -> blank
captures. Broaden to (TARGET_OS_MACCATALYST || TARGET_OS_TV) so tvOS gets the
programmatic METALView + the viewDidLayoutSubviews Metal-sync hook.
2. cn1_copyMetalScreenTextureImage + its call site in cn1_renderViewIntoContext
were also MACCATALYST-only. On a headless simulator -drawViewHierarchyInRect:
snapshots a stale/blank CALayer (the display link never presents), so the
screenTexture readback is required. Broaden to tvOS, and stage into a Shared
texture there (MTLStorageModeManaged / -synchronizeResource: are macOS-only;
tvOS is unified-memory like iOS).
Verified on the Apple TV 4K simulator: loadView runs (self.view = METALView),
the capture reads the live screenTexture, and the suite now streams 100 unique
images of 128 (was 3) -- chart-pie etc. render correctly. iOS unaffected (both
guards are additive || TARGET_OS_TV; iphonesimulator BUILD SUCCEEDED).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 tvOS screenshots captured locally now that the Metal pipeline renders real content (100 unique; remaining are tests that legitimately share a frame, same as the iOS suite's duplicate_image_with set). build-ios-tv stays non-blocking for this push so CI can compare its own captures against these and confirm local==CI rendering before the job is flipped to a hard gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Compared 128 screenshots: 128 matched. |
…tv to a hard gate The first golden set was captured on a local Apple TV 4K simulator that rendered at 1080x2206 (portrait); CI renders the genuine 4K landscape framebuffer (3840x2160), so 0 of 127 matched -- the canonical 'local is unfaithful to CI' trap. Reseed all 128 goldens from the build-ios-tv CI capture (127 unique; chart-pie etc. verified to render real content), which is what the job compares against. Flip build-ios-tv off continue-on-error so a mismatch now fails the job (a real gate like build-ios-watch), and tighten CN1SS_ALLOWED_MISSING from the placeholder 9999 to 4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rior test's title)
The first golden for this test was captured during a title-update race and
showed the previous test's title ('graphics-fill-round-rect'). The gate run
captured the correct title; reseed from it so the golden matches. The other 127
goldens matched cleanly across two independent CI runs, so they are reliable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pe in TVOS_PORT.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On the CI tvOS Metal slice DrawRoundRect intermittently times out at the show-completed stage and then crashes the app with SIGBUS (signal 10) on retry, which takes the rest of the suite down with it (the remaining ~127 tests then report missing). It renders correctly most of the time, so this is a tvOS Metal round-rect stability bug to root-cause as a follow-up. Skip it on tvOS only (CN.isTV(), gated inline to avoid the static-init class-loading pitfall noted on shouldForceTimeoutInHtml5) so the suite completes, and remove the tvOS golden so it is not counted as missing. Coverage stays on the phone/tablet ports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s golden" This reverts commit 107d1fe.
Temporary: dump the faulting backtrace (CN1TVCRASH) the first few times the tvOS signal handler fires, to locate the DrawRoundRect SIGBUS that intermittently crashes the tvOS screenshot suite. Reverted once the root cause is found. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agnostic)" This reverts commit 8dff7c1.
…xes tvOS DrawArc/RoundRect SIGABRT) Root cause (found by instrumenting the signal handler + the mask allocation): the tvOS screenshot suite crashed with SIGABRT inside -[MTLTextureDescriptorInternal validateWithDevice:] -- 'MTLTextureDescriptor has width (15584) greater than the maximum allowed size of 8192'. createAlphaMask builds the alpha-mask texture from the full shape.getBounds(); the graphics-draw-arc / graphics-draw-round-rect tests draw stroked paths that legitimately extend far off-screen, so at 4K (tvOS renders 3840x2160; the sim caps textures at 8192) the mask width reached ~15584 and aborted the whole app, killing the rest of the suite. iOS-Metal never hit it because the iPhone-sized masks stay well under the limit. Fix: Renderer_clampOutputExtent caps the renderer's output extent (right/bottom bounds) so the mask width/height never exceed CN1_PATH_MASK_MAX_DIM (8192). getSubpixMaxX already clamps the path edge to boundsMaxX and the scanline fill clamps crossings to the same bounds, so the texture, the produced alphas and getOutputBounds stay consistent. It is lossless for visible pixels: anything past the cap is far outside the framebuffer (3840px) and clipped by the scissor regardless. Applied to every alpha-mask path (Metal, GL/ES2, watch CG, and the ARGB conversion) so no backend can request an over-max / multi-GB mask. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>






























































































































Adds TV form-factor support across the framework, builders and CI. Modeled on the merged Apple Watch port (#5252), but tvOS is handled like the Mac Catalyst slice (Metal + most iOS APIs, just no OpenGL ES) rather than the watch's Core-Graphics backend.
Phase 1 — shared form-factor API +
@media(complete, verified)CN.isTV()/Display.isTV()/CodenameOneImplementation.isTV()(mirrorsisWatch()).isTV()via a newisRunningOnTV()native backed byTARGET_OS_TV;{tv,ios,appletv}platform overrides.isTV()(television/leanback feature +UiModeManagerfallback);{tv,android,android-tv}overrides.Resources.loadThemenow selectsdevice-tv/device-watch@mediavariants at runtime — this also completes@mediafor the existing watch port, which never wired it.CSSDeviceFormFactorMediaQueryTest(passes against the real compiler) +css.asciidocdocs.Phase 2 — Google TV / Android TV (complete, verified)
AndroidGradleBuilder(both the maven-plugin and BuildDaemon copies):LEANBACK_LAUNCHERcategory,android.software.leanback+ touchscreen-optionaluses-feature, and a generated 320×180tv_banner, gated on a newandroid.tvhint.android.tv/android.tv.banner); README + newTVPlatforms.asciidocdev-guide section.Phase 3 — Apple TV (tvOS)
TvNativeBuilderadds a separateappletvosXcode target (TARGETED_DEVICE_FAMILY=3, Metal, OpenGL-only.mfiles excluded), reusing the sharedUIApplicationMainentry — modeled onMacNativeBuilder. Wired intoIPhoneBuilder+CN1BuildMojo(codename1.tvMain) + build-hint schema.<Main>TVtarget generates and the build reaches compilation.#if !TARGET_OS_TVslice in progress: MessageUI + several UIKit-conformance guards landed; the remaining guards (notifications, WebKit, status-bar/orientation, pickers) are tracked inPorts/iOSPort/nativeSources/TVOS_PORT.md.Phase 4 — tvOS screenshot tests
scripts/run-tv-ui-tests.sh(tvOS-simulator clone ofrun-watch-ui-tests.sh) + a non-blockingbuild-ios-tvCI job (continue-on-erroruntil the native slice compiles and thescripts/ios/screenshots-tvgolden set is seeded — the same bootstrap the watch port used). Sample auto-enables the target viacodename1.tvMain.Status / follow-up
Phases 1–2 are complete and verified. The tvOS builder + test infrastructure are complete and verified to generate a valid tvOS target; the tvOS native source slice needs further
!TARGET_OS_TVguarding (documented inTVOS_PORT.md) beforebuild-ios-tvgoes blocking and seeds goldens. CorrespondingTvNativeBuilderchanges were also made in the BuildDaemon repo (cloud builds).🤖 Generated with Claude Code