Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Move Android in-app purchase to the Play Billing ProductDetails API - #5651

Merged
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details
Sep 1, 2026
Merged

Move Android in-app purchase to the Play Billing ProductDetails API#5651
shai-almog merged 4 commits into
masterfrom
fix/play-billing-product-details

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.

What broke

A customer added Play Billing 9.1.0 and got a wall of cannot find symbol errors naming com.codename1.impl.android.BillingSupport — a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.

Compiling Ports/Android/src/com/codename1/impl/android/BillingSupport.java against each published release (android-34 android.jar + our core jar), counting only errors in that file:

billingbeforeafter
4.0.0 – 7.1.10does not compile
8.0.070
9.1.0240

This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was 4.0.0, rejected since August 2024.

Six removals, not one

SkuDetails is just the loudest:

what it didremoved in
enablePendingPurchases() no-arg8.0.0
queryPurchasesAsync(String, listener)8.0.0
querySkuDetailsAsync ×5 call sites8.0.0
SkuDetails / SkuDetailsParams / SkuDetailsResponseListener9.0
BillingClient.SkuType9.0
BillingFlowParams.Builder.setSkuDetails9.0

Billing 8 and 9 share the modern shape (QueryProductDetailsResult, PendingPurchasesParams), so one implementation serves both.

Two judgement calls

  • There is no single price. A one-time product carries one offer; a subscription carries offers carrying pricing phases. SkuDetails.getPrice() maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and to null when Play sends neither, so a caller shows no price rather than a wrong one.
  • A subscription is bought through an offer.launchBillingFlow rejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing.

Builder half

  • android.billingclient.version default 4.0.08.0.0 (lowest Play still accepts, and the only version at that level whose AAR is content with minSdkVersion 21 — every later release needs 23).
  • Below the floor: refused with a sentence naming the hint and the way out, rather than letting javac fail inside the generated project.
  • minSdk raised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure the raise exists to prevent.

The missing gate

maven/android compiled this file against android-billing-4.0.0.jar under -Pcompile-android — which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.

That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (loadSkuDetailsAsync had kept its old name).

Verification

  • BillingSupport.java compiles clean against 8.0.0 and 9.1.0; every API shape checked with javap against both AARs.
  • mvn -Pcompile-android -pl android package — BUILD SUCCESS; confirmed no BillingSupport.class is produced and the .java still ships in the port jar.
  • PlayBillingVersionsTest — 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).
  • Full codenameone-maven-plugin suite 1854/0; SpotBugs 0 on android and codenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.

Not verified

No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.

Companion: codenameone/BuildDaemon#PLACEHOLDER

Reported through support: an app adding Play Billing 9.1.0 got a wall of
"cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a
file the developer never wrote. That file was written against the SKU API, which
Play Billing removed. Compiling it against every published release places the
break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's
deprecation FAQ states that from Aug 31 2026 all new apps and updates must use
billing 8 or later, with an extension to Nov 1 2026. Every Codename One app
using in-app purchase on Android was therefore unable to ship a Play-accepted
update.
billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after
billing 8.0.0, 9.1.0 24 errors before this change, 0 after
Six separate removals had to be answered, not just the SkuDetails one the error
names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..),
querySkuDetailsAsync at five call sites, BillingClient.SkuType,
BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8
and 9 share the modern shape, so one implementation serves both.
Two things the ProductDetails API has no direct equivalent for, decided rather
than guessed:
- There is no single price. A one-time product carries one offer; a subscription
carries offers that carry pricing phases. The old SkuDetails.getPrice() is
mapped to the one-time offer's formatted price, or a subscription's first
phase of its first offer, and to null when Play sends neither -- so a caller
shows no price rather than a wrong one.
- A subscription is bought through an offer, and launchBillingFlow rejects one
with no offer token, while a one-time product must not carry a token it did
not ask for. Both cases are handled, and a subscription with no purchasable
offer now reports itemPurchaseError instead of silently doing nothing.
The builder half, in both this repo and the daemon: the default
android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the
floor is refused with a sentence naming the hint and the way out rather than
letting javac fail in the generated project, and minSdk is raised to whatever
the selected billing AAR declares. That last one is not decoration -- the AAR
manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge
fails outright against a lower value. It is resolved before the manifest is
written, not beside the gradle dependency it feeds; resolving it late put the
raised floor in build.gradle and left the manifest saying 19, which is the merge
failure it was meant to prevent.
The port module no longer compiles BillingSupport, and drops the dead
android-billing-4.0.0.jar it compiled against. It ships as source and compiles
inside the app build, like the four packages already excluded there for the same
reason. That removes the last compile that touched this file, so the check moves
to a test that names the removed API and fails if any of it comes back -- weaker
than a compile, and it says so, but it is the difference between catching this
in CI and hearing about it from a support conversation. It caught one leftover
already: loadSkuDetailsAsync kept its old name.
Nothing here has been exercised against a real purchase. The API shapes are
verified against the published AARs and the file compiles clean against 8.0.0
and 9.1.0, but an actual buy, acknowledge and consume against a Play test
account still needs a device pass before this ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-09-01T11:08:37.501529Zc0a4499New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:971c6d2795

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

… maven
The BuildDaemon CI builds this port with ant against the companion CodenameOne
branch, so excluding BillingSupport in maven/android/pom.xml alone left that
build compiling the ProductDetails source against the billing 4.0.0 jar and
failing on the imports. Three places compile this port and each one's comment
says it mirrors the other two; only one of them had been updated.
The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with
it, matching the pom.
A check reads all three files, because "mirrors the other two" written in three
comments is not a mechanism, and the one that drifted was found by CI rather
than by anything here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actionsBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs[Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…r taking the low minSdk
Two review findings on the billing change.
A one-time product can carry offers now, not just subscriptions. When it does,
launchBillingFlow has to be told which offer is being bought or it rejects the
flow -- the same rule subscriptions have always had, which is why the helper was
called subscriptionOfferToken and returned null for everything else. It reads
both kinds now: getOneTimePurchaseOfferDetails for the default offer,
getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are
present from billing 8.0.0, so this still compiles from one source against 8.0.0,
8.3.0 and 9.1.0 -- checked, 0 errors on each.
A token is passed only when Play actually supplied one, so a product with no
offers still launches with no token exactly as before. The default offer is
preferred over the list because it is the one the removed setSkuDetails call
would have bought.
Separately, android.billingclient.version accepts a Gradle dynamic selector, and
the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while
its numeric prefix reads as "8". The range check answered that with the
8.0.0-only floor of 21, and the manifest merge then failed against the library's
own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now
matched exactly, so a selector, a range and an unseen version all take the high
floor, and 8.0.0 itself still takes 21.
Neither is reachable by the existing tests, so both got cases, including one
pinning that the exact match did not simply collapse every version to 23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:60f93f5465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadPorts/Android/src/com/codename1/impl/android/BillingSupport.java Outdated
The offer-token fix left the price and the purchase reading different offers: the
price came from the default offer while the flow launched the first tokenized
entry from the list, so a product configured with offers and no base showed one
price and charged another -- or showed no price at all while buying happily.
One selection now answers both. The default offer still wins when Play sends
one, because that is what buying with no token selects and what the removed
setSkuDetails call would have bought; the list decides only when there is no
default, and then the same entry supplies the price and the token.
Still compiles clean against billing 8.0.0 and 9.1.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.10% (9017/99118 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (46522/523733), branch 3.51% (1740/49629), complexity 3.48% (1843/52924), method 5.34% (1487/27841), class 10.74% (400/3723)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

MetricDuration
SIMD kernel backendscalar fallback (no native SIMD)
SIMD int-add (64K x300)java 161ms / native 130ms = 1.2x speedup
SIMD float-mul (64K x300)java 138ms / native 36ms = 3.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode59.000 ms
Base64 CN1 decode67.000 ms
Base64 native encode312.000 ms
Base64 encode ratio (CN1/native)0.189x (81.1% faster)
Base64 native decode243.000 ms
Base64 decode ratio (CN1/native)0.276x (72.4% faster)
Image encode benchmark statusskipped (SIMD unsupported)

@shai-almog
shai-almog merged commit 6d06ef2 into masterSep 1, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/play-billing-product-details branch September 1, 2026 11:41
@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 160 screenshots: 160 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 815 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 108ms / native 7ms = 15.4x speedup
SIMD float-mul (64K x300)java 101ms / native 2ms = 50.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode170.000 ms
Base64 CN1 decode95.000 ms
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)3.000 ms
Image createMask ratio (SIMD on/off)0.429x (57.1% faster)
Image applyMask (SIMD off)69.000 ms
Image applyMask (SIMD on)88.000 ms
Image applyMask ratio (SIMD on/off)1.275x (27.5% slower)
Image modifyAlpha (SIMD off)60.000 ms
Image modifyAlpha (SIMD on)55.000 ms
Image modifyAlpha ratio (SIMD on/off)0.917x (8.3% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)55.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.917x (8.3% faster)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2093 seconds

Build and Run Timing

MetricDuration
Simulator Boot95000 ms
Simulator Boot (Run)1000 ms
App Install24000 ms
App Launch4000 ms
Test Execution519000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 63ms / native 3ms = 21.0x speedup
SIMD float-mul (64K x300)java 60ms / native 2ms = 30.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode203.000 ms
Base64 CN1 decode149.000 ms
Base64 native encode450.000 ms
Base64 encode ratio (CN1/native)0.451x (54.9% faster)
Base64 native decode330.000 ms
Base64 decode ratio (CN1/native)0.452x (54.8% faster)
Base64 SIMD encode69.000 ms
Base64 encode ratio (SIMD/CN1)0.340x (66.0% faster)
Base64 SIMD decode45.000 ms
Base64 decode ratio (SIMD/CN1)0.302x (69.8% faster)
Base64 encode ratio (SIMD/native)0.153x (84.7% faster)
Base64 decode ratio (SIMD/native)0.136x (86.4% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)49.000 ms
Image applyMask (SIMD on)47.000 ms
Image applyMask ratio (SIMD on/off)0.959x (4.1% faster)
Image modifyAlpha (SIMD off)41.000 ms
Image modifyAlpha (SIMD on)33.000 ms
Image modifyAlpha ratio (SIMD on/off)0.805x (19.5% faster)
Image modifyAlpha removeColor (SIMD off)38.000 ms
Image modifyAlpha removeColor (SIMD on)44.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.158x (15.8% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2522 seconds

Build and Run Timing

MetricDuration
Simulator Boot100000 ms
Simulator Boot (Run)1000 ms
App Install18000 ms
App Launch3000 ms
Test Execution537000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 77ms / native 4ms = 19.2x speedup
SIMD float-mul (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode394.000 ms
Base64 CN1 decode240.000 ms
Base64 native encode1074.000 ms
Base64 encode ratio (CN1/native)0.367x (63.3% faster)
Base64 native decode377.000 ms
Base64 decode ratio (CN1/native)0.637x (36.3% faster)
Base64 SIMD encode73.000 ms
Base64 encode ratio (SIMD/CN1)0.185x (81.5% faster)
Base64 SIMD decode85.000 ms
Base64 decode ratio (SIMD/CN1)0.354x (64.6% faster)
Base64 encode ratio (SIMD/native)0.068x (93.2% faster)
Base64 decode ratio (SIMD/native)0.225x (77.5% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)9.000 ms
Image createMask ratio (SIMD on/off)0.529x (47.1% faster)
Image applyMask (SIMD off)108.000 ms
Image applyMask (SIMD on)639.000 ms
Image applyMask ratio (SIMD on/off)5.917x (491.7% slower)
Image modifyAlpha (SIMD off)509.000 ms
Image modifyAlpha (SIMD on)227.000 ms
Image modifyAlpha ratio (SIMD on/off)0.446x (55.4% faster)
Image modifyAlpha removeColor (SIMD off)278.000 ms
Image modifyAlpha removeColor (SIMD on)451.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.622x (62.2% slower)

@shai-almog

shai-almog commented Sep 1, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 58ms / native 3ms = 19.3x speedup
SIMD float-mul (64K x300)java 59ms / native 3ms = 19.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode160.000 ms
Base64 CN1 decode91.000 ms
Base64 native encode675.000 ms
Base64 encode ratio (CN1/native)0.237x (76.3% faster)
Base64 native decode249.000 ms
Base64 decode ratio (CN1/native)0.365x (63.5% faster)
Base64 SIMD encode51.000 ms
Base64 encode ratio (SIMD/CN1)0.319x (68.1% faster)
Base64 SIMD decode44.000 ms
Base64 decode ratio (SIMD/CN1)0.484x (51.6% faster)
Base64 encode ratio (SIMD/native)0.076x (92.4% faster)
Base64 decode ratio (SIMD/native)0.177x (82.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)9.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.222x (77.8% faster)
Image applyMask (SIMD off)57.000 ms
Image applyMask (SIMD on)39.000 ms
Image applyMask ratio (SIMD on/off)0.684x (31.6% faster)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)36.000 ms
Image modifyAlpha ratio (SIMD on/off)0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off)46.000 ms
Image modifyAlpha removeColor (SIMD on)35.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.761x (23.9% faster)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog