Skip to content

Expose the pro auto-renewing config key to Kotlin - #49

Draft
mpretty-cyro wants to merge 1 commit into
session-foundation:mainfrom
mpretty-cyro:feature/pro-auto-renewing-glue
Draft

Expose the pro auto-renewing config key to Kotlin#49
mpretty-cyro wants to merge 1 commit into
session-foundation:mainfrom
mpretty-cyro:feature/pro-auto-renewing-glue

Conversation

@mpretty-cyro

Copy link
Copy Markdown
Collaborator

LibSession-Util-Android @ feature/pro-auto-renewing-glue → base main (a00aa4a).

One commit, 4 files: a JNI pair, two Kotlin declarations, and a submodule pin bump.

🔴 Cannot merge until libsession-util #121 merges

The libsession-util submodule is pinned to 8e5634b8, which is the head of jagerman's
#121 — an OPEN PR.
Merging this
first would pin the wrapper to a commit that isn't on dev.

A reviewer looking at a glue diff has no reason to have #121 in mind, so it is stated here as well as
in the commit message.

Not the -pfs branch. JasonFork/pro-auto-renewing-config-pfs carries an identical commit
subject with a different sha and is not a descendant — it rebases the same change onto the PFS
track, dragging that line in with it. gh pr view 121 --json headRefOid confirms 8e5634b8.

Merge order: #121 → this → a LibSession-Util-Android release → the Android client's
libsessionUtilAndroidVersion bump → the client PR (session-android
feature/pro-status-refresh-unification), which is what needs these accessors.

Why this exists

#121 adds the A / auto_renewing user-profile key to core only. This wrapper had no binding
for it, so nothing on the client side could read or write it — "the core PR is merged" is not the same
as "the accessor is reachable from Kotlin". The client work needs to persist auto_renewing into
synced config alongside the access expiry, so a linked device has the account state without its own
fetch.

Follows the existing ProAccessExpiry shape exactly: two extern "C" functions, two interface
members, two external overrides. No new pattern.

Known limitation — inherent to A, surfaced here

A is presence-only. set_pro_auto_renewing(false) erases the key rather than storing zero,
so through this accessor a stored false and "never written" are indistinguishable —
getProAutoRenewing() returns false for both.

That is #121's encoding, not something this binding introduces, but this is where a Kotlin caller
meets it, so it is documented on the accessor itself rather than only at the call sites. Callers that
need to tell the two apart cannot, through this API.

Not included, and conditional: a mirror of user_profile_has_pro_auto_renewing() — the predicate
that does distinguish them. It exists on an unpushed libsession branch and is only needed if the
client's startup gate is ruled to bootstrap a fetch when A has never been written. This PR does
not call it and is complete without it.
If that ruling lands it is a further ~20 lines here, the
same shape as the pair below.

Verification — and what it does not rest on

A JNI signature mismatch is invisible at compile time and traps at runtime, so a green build is
not evidence for a change of this kind, and a reviewer cannot see the binding is correct from the
diff. Checked against the built artefact instead — app-play-debug.apk, all four ABIs:

arm64-v8a:   getProAutoRenewing=1  setProAutoRenewing=1
armeabi-v7a: getProAutoRenewing=1  setProAutoRenewing=1
x86:         getProAutoRenewing=1  setProAutoRenewing=1
x86_64:      getProAutoRenewing=1  setProAutoRenewing=1

APK built from this branch with the consuming client change, 30 seconds before the check.
:app:assemblePlayDebug green; the client's suite is 217 tests / 0 failures against it.

The leg not run: there is no unit test for the binding in this repo. No JVM test here can load
libsession_util.so, which is why the evidence is a symbol check on the real artefact rather than a
test. Stating it rather than letting "verified" cover a check that wasn't possible.

Reviewer notes

  • The int-returning ..._opt() sentinel shape that circulated earlier is not what this uses.
    On Android the natural binding — mirroring the accessor directly above it — would be
    static_cast<jboolean>(-1) = 255 = JNI_TRUE, so "never stored" would silently reach Kotlin as
    "auto-renewing". jboolean is uint8_t; the narrowing is legal and produces no diagnostic. The
    sentinel was dropped from #121's follow-up on that evidence.
  • No t/T bump on the write path: auto_renewing is backend-derived state like E, I and R,
    not a user profile edit, and libsession omits the bump for it deliberately.

libsession PR #121 adds the `A` / auto_renewing user-profile key, but it is core-only — this
wrapper had no binding for it, so clients could not read or write it. Adds the JNI pair and
the Kotlin declarations, following the existing ProAccessExpiry shape.

The accessor is presence-only and the doc comment says so at the API boundary rather than
only at the call site: set_pro_auto_renewing(false) ERASES the key, so `false` and "never
written" are the same state through this getter. That is #121's encoding surfacing here, not
something introduced by the binding, and it is where the next reader will meet it.

Pins the libsession-util submodule to 8e5634b8, the head of #121, which is UNMERGED — so this
commit cannot merge until #121 does. It is also not the identically-subjected
pro-auto-renewing-config-pfs commit, which rebases the same change onto the PFS track.

Verified in the built APK across all four ABIs rather than by a successful compile: a JNI
signature mismatch is invisible at compile time and only traps at runtime.
@mpretty-cyro mpretty-cyro self-assigned this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant