Repository navigation
Android bridge without the Java wrapper, harness on a device - #36
Merged
Merged
Conversation
Contributor
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e142ac2aa
ℹ️ 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".
yauch-dev
force-pushed
the
feature/android-bridge-without-wrapper
branch
2 times, most recently
from
October 28, 2026 15:33
0582d75 to
1af8ed3
Compare
yauch-dev
force-pushed
the
feature/android-bridge-without-wrapper
branch
from
October 28, 2026 18:51
1af8ed3 to
828aa5d
Compare
yauch-dev
force-pushed
the
feature/android-bridge-without-wrapper
branch
2 times, most recently
from
November 9, 2026 20:27
5989dc2 to
161f587
Compare
vladd-g
approved these changes
Nov 12, 2026
AdaptyAndroid.cs reaches io.adapty.internal:crossplatform through JNI and implements its four callback interfaces as AndroidJavaProxy, so the package ships no Java of its own: the adaptyandroidwrapper Gradle project, the unity-wrapper AAR under Plugins/Android/Local and AdaptyAndroidCallbackAction go away, and a native bump is one line in the androidlib build.gradle. Callbacks are posted to a Handler on the Unity thread's Looper, as the wrapper did, not through RunOnMainThread: a flow is an Activity of its own, Unity's player loop is paused underneath it, and a SynchronizationContext post would wait for the frame that comes only once the flow is gone - the close action would reach the listener with nothing left to dismiss. Measured on a device with the SDK harness against the wrapper this replaces. R8 needs no rule of its own: android-sdk's consumer rules keep com.adapty.**.
harness_build_android builds the harness scene as a Development ARM64 player. The stock Pipeline runtime server cannot run on a device - it writes its descriptor next to Application.dataPath, which is the APK - so AndroidHarnessServer turns the driver's autostart off and runs a subclass that writes the descriptor into the app's internal files directory, with a token of its own. The host pulls it with run-as, rewrites pid and heartbeat to what the CLI accepts, and bridges the port with adb forward; the harness AGENTS.md has the steps and the pause behaviour under a flow. Pipeline 0.6.0-exp.1 starts the runtime server from Project Settings > Pipeline > Runtime rather than a scene component; the settings file enables it on port 7900.
…nced Pipeline 0.6.0 has no such component; the runtime server is configured in Project Settings > Pipeline > Runtime, and the scene showed a missing script.
Under GameActivity - the default entry point of a new Unity 6 project - Unity's scripting thread has no Java Looper, so the bridge threw on its first use and no call reached the SDK; 4.1.0-4.1.2 with the Java wrapper registered nothing and dropped every completion and event instead. While a flow is on screen GameActivity also stops the player loop, SynchronizationContext and UnityPlayer.invokeOnMainThread alike, so a RunOnMainThread fallback would leave a flow that cannot be closed. AdaptyAndroidLooper registers an eventfd on the thread's native ALooper through P/Invoke into libc and libandroid, and drains its queue in the looper callback. That looper keeps turning through the pause on both entry points - Looper.loop() polls it under UnityPlayerActivity, the activity glue under GameActivity - so it replaces the Handler outright; a Handler on Looper.myLooper() would also bind to a Looper nobody loops if something called Looper.prepare() there. Checked on an Android 17 emulator: a flow closed from the flow's close action and from system Back while Unity was paused, on both entry points with Unity 6000.4, GameActivity also on 6000.0 and 6000.6. The AGENTS.md #if count is corrected to what the package holds.
The Pipeline 0.6 build processor reads EditorUserBuildSettings.development rather than the build options, on every platform. Only the Android harness build set it, so an iOS harness build from an Editor with the flag off stopped on the processor's release-build dialog, which nothing driving the Editor from the CLI can answer.
BuildAndroid did not set the flag the linker processor reads, so the Android harness was built with an empty list and the linker could strip what the harness reaches only by reflection.
They still said there was no Android build, that the scene referenced RuntimePipelineManager, and that the 120 s request timeout was the scene's; it now comes from RuntimePipelineConfig.json. An iOS device is what stays out of reach.
…ess's Pipeline 0.6 puts no define constraint on its runtime assembly, so with Enable In Builds on in the project every player got the server - a release build in batchmode with only a warning, the demo's and StrippingBuild's included - and AndroidHarnessServer, which lives in Assembly-CSharp, started it on any Android player. The project now keeps Enable In Builds off. Both harness builds turn it on for the build, with the Development flag, and restore both together with the iOS SDK, the Android architectures and the application id they set. Without a driver AndroidHarnessServer returns quietly, since that is every player that is not a harness build. The settings file is committed as RuntimePipelineConfig.Save writes it, so a harness build leaves it unchanged.
…ndroid A permission respond and the observer-mode reports are app code that may run on any thread, and RunOnMainThread posted them through the SynchronizationContext. On Android a flow pauses Unity's player loop, so the answer waited for the flow it was meant for to close. They now go through AdaptyAndroidLooper, like the bridge's own callbacks. The activity provider is set on every initialisation of the helper, as in the other cross-platform SDKs, not only on the process's first. The changelog tells a .unitypackage install to delete Plugins/Android/Local, where the old wrapper's AAR would otherwise stay in the build.
…thread An app answers a permission request from the OS callback and reports an observer-mode purchase from the store's, neither of them on Unity's thread. The harness answered both inline on the main thread, so it never took the path RunOnMainThread exists for; now it answers from a pool thread and logs that it did, and a run on an Android device checks that the answer reaches a flow that is on screen.
…e Editor The post-processor that files the local StoreKit configuration is compiled for the platform the Editor is on. After an Android harness build in a live Editor, harness_build produced a project without it and purchases went to the sandbox with nothing saying why; it now refuses to run unless the Editor is on iOS. AndroidHarnessServer was guarded by UNITY_ANDROID alone, so in Play Mode on the Android platform with Enable In Builds on it took over the driver's server and then threw on AndroidJavaClass. It is now compiled for Android players only.
x401om
force-pushed
the
feature/android-bridge-without-wrapper
branch
from
November 13, 2026 13:45
161f587 to
2ded1cb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
CrossplatformHelperfrom C#.AdaptyAndroid.csreachesio.adapty.internal:crossplatformthrough JNI and implements its four callback interfaces asAndroidJavaProxy. TheadaptyandroidwrapperGradle project, theunity-wrapperAAR underPlugins/Android/LocalandAdaptyAndroidCallbackActionare gone; a native bump is one line in the androidlibbuild.gradle. R8 needs no rule:android-sdk's consumer rules keepcom.adapty.**. The activity provider is set on every initialisation, as in the other cross-platform SDKs.AdaptyAndroidLooper, aneventfdon the thread'sALooper). A flow is an Activity of its own and Unity's player loop is paused underneath it; aSynchronizationContextpost would wait for the frame that comes only once the flow is gone, so thecloseaction would reach the listener with nothing left to dismiss. Unlike aHandleronLooper.myLooper(), the native looper is there under GameActivity too, where Unity's thread has no JavaLooperand 4.1.0–4.1.2 delivered nothing.RunOnMainThread, which carries the permissionrespondand the observer-mode reports from app code, posts through the same looper on Android, so they reach a flow that is still on screen.harness_build_androidandAndroidHarnessServer(the stock Pipeline runtime server writes its descriptor next to the APK and stops); the run steps are in the harnessAGENTS.md. Pipeline is bumped to 0.6.0-exp.1, which starts the runtime server from Project Settings rather than a scene component; the scene's staleRuntimePipelineManageris removed. Enable In Builds stays off in the project, because in 0.6 it puts the server into any player built while it is on, release builds included; both harness builds turn it on for the build and restore it, with every other setting they touch..unitypackageinstall deletesAssets/AdaptySDK/Plugins/Android/Localbefore importing, or the old wrapper's AAR stays in the build.Verified
flow_view_did_perform_action(system_back) delivered to C# within 3 s while the flow is on screen — identical on both builds.observerModeHandler: onStartPurchase called) while the flow is on screen;system_backstill arrives. On the iOS simulator the harness build runs the same calls and the observer purchase.RuntimePipelineConfig.jsonandProjectSettings.assetunchanged.respond(no test flow asks for one; it takes the sameRunOnMainThreadpath), and a purchase through the flow outside Observer mode.