Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET 7 UnsatisfiedLinkError to native OnCreate() coming from Java Binding #8675

Open
bloomer-joe opened this issue Jan 25, 2024 · 36 comments
Open
Assignees
Labels
Area: App Runtime Issues in `libmonodroid.so`.

Comments

@bloomer-joe
Copy link

Android application type

.NET Android (net7.0-android, net8.0-android, etc.)

Affected platform version

Android 12

Description

App shows UnsatisfiedLinkError after migrating from Mono.Android v12.0 (API 31) to net7.0-android. The app contains multiple projects to link in 3rd party Java bindings. The app (including bindings) runs smoothly except for one feature. When that feature is called from the Java binding, it attempts to call the app's OnCreate() and fails.

It seems as if OnCreate() might not be registered or otherwise accessible from the Java code,.

Advice from these documents was introduced as a possible fix, but the error persists:
https://learn.microsoft.com/en-us/dotnet/maui/android/internals/java-interop?view=net-maui-7.0#registration
https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/native-embedding?view=net-maui-7.0

Due to the nature of the 3rd party library, this bug is only caught in Release mode, as the library's Debug mode implementation does not allow hardware activation of the NFC reader.

Steps to Reproduce

No repro.

Did you find any workaround?

No response

Relevant log output

java.lang.UnsatisfiedLinkError: No implementation found for void crc6443da931ec5bafba4.MainApplication.n_onCreate() (tried Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate and Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate__)
                                                                                                    	at crc6443da931ec5bafba4.MainApplication.n_onCreate(Native Method)
                                                                                                    	at crc6443da931ec5bafba4.MainApplication.onCreate(MainApplication.java:25)
                                                                                                    	at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1211)
                                                                                                    	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6715)
                                                                                                    	at android.app.ActivityThread.access$1300(ActivityThread.java:250)
                                                                                                    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2045)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:106)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:201)
                                                                                                    	at android.os.Looper.loop(Looper.java:288)
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:7829)
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:982)
@bloomer-joe bloomer-joe added Area: App Runtime Issues in `libmonodroid.so`. needs-triage Issues that need to be assigned. labels Jan 25, 2024
@grendello
Copy link
Contributor

@bloomer-joe could you try turning linking off (set the PublishTrimmed MSBuild property to false) and see if the application works correctly then?

@grendello grendello added need-info Issues that need more information from the author. and removed needs-triage Issues that need to be assigned. labels Jan 25, 2024
@jonpryor
Copy link
Member

jonpryor commented Jan 25, 2024

@bloomer-joe: a repro would also be handy. Full (-ish) adb logcat output would also be handy.

The crc6443da931ec5bafba4.MainApplication methods should be registered by ApplicationRegistration.registerApplications(), which is generated at packaging time in e.g. obj/Release/net8.0-android/android/src/mono/android/app/ApplicationRegistration.java, and is called by MonoPackageManager.LoadApplication():

https://github.com/xamarin/xamarin-android/blob/a832dd8d2ad834361ee8b0708a81547edbad797a/src/java-runtime/java/mono/android/MonoPackageManager.java#L128

My suspicion is that MonoPackageManager.LoadApplication() isn't being called in your codepath. MonoPackageManager.LoadApplication() is called by MonoRuntimeProvider:

https://github.com/xamarin/xamarin-android/blob/a832dd8d2ad834361ee8b0708a81547edbad797a/src/Xamarin.Android.Build.Tasks/Resources/MonoRuntimeProvider.Bundled.java#L35

MonoRuntimeProvider in turn is within your (generated) AndroidManifest.xml, within a <provider/> element:

<provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="1999999999" android:authorities="Mono.Android.NET_Tests.mono.MonoRuntimeProvider.__mono_init__" />

I see from the Java-side callstack that MainApplication.onCreate() is called by Instrumentation.callApplicationOnCreate(). Perhaps this Instrumentation-based startup path is not initializing providers?

@bloomer-joe
Copy link
Author

bloomer-joe commented Jan 26, 2024

@grendello , I was thinking the same thing about the "PublishTrimmed" property, but modifying that did not change the outcome.

A more complete logcat:

01-26 11:42:28.030  1781  1875 I ActivityManager: Start proc 29427:com.stripe.cots.aidlservice/u0a318 for service 
/{com.bloomerang.stripe.demo/com.stripe.cots.aidlservice.CotsService}
01-26 11:42:28.042 29427 29427 I ots.aidlservice: Using CollectorTypeCC GC.
01-26 11:42:28.047 29427 29427 E ots.aidlservice: Not starting debugger since process cannot load the jdwp agent.
01-26 11:42:28.054 29427 29427 D CompatibilityChangeReporter: Compat change id reported: 171979766; UID 10318; state: ENABLED
01-26 11:42:28.059 29427 29427 W ziparchive: Unable to open '/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.arm64_v8a.dm': No such file or directory
01-26 11:42:28.059 29427 29427 W ziparchive: Unable to open '/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.arm64_v8a.dm': No such file or directory
01-26 11:42:28.059 29427 29427 W ots.aidlservice: Entry not found
01-26 11:42:28.060 29427 29427 W ziparchive: Unable to open '/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.en.dm': No such file or directory
01-26 11:42:28.060 29427 29427 W ziparchive: Unable to open '/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.en.dm': No such file or directory
01-26 11:42:28.060 29427 29427 W ots.aidlservice: Entry not found
01-26 11:42:28.060 29427 29427 W ziparchive: Unable to open '/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.xxhdpi.dm': No such file or directory
01-26 11:42:28.060 29427 29427 W ziparchive: Unable to open '/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.xxhdpi.dm': No such file or directory
01-26 11:42:28.061 29427 29427 W ots.aidlservice: Entry not found
01-26 11:42:28.062 29427 29427 D nativeloader: Configuring clns-4 for other apk /data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/base.apk:/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.arm64_v8a.apk:/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.en.apk:/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.xxhdpi.apk. target_sdk_version=33, uses_libraries=, library_path=/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/lib/arm64:/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/base.apk!/lib/arm64-v8a:/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.arm64_v8a.apk!/lib/arm64-v8a:/data/app/~~uBYWYGCriL5wwpNVSC6LRg==/com.bloomerang.stripe.demo--gy7gOwkih_wdTRhEpAyvA==/split_config.en.apk!/lib/arm64-v8a:/data/app/~~uBYWYGCriL5wwpNVSC6LR
01-26 11:42:28.068 29427 29427 V GraphicsEnvironment: ANGLE Developer option for 'com.bloomerang.stripe.demo' set to: 'default'
01-26 11:42:28.068 29427 29427 V GraphicsEnvironment: ANGLE GameManagerService for com.bloomerang.stripe.demo: false
01-26 11:42:28.068 29427 29427 V GraphicsEnvironment: Updatable production driver is not supported on the device.
01-26 11:42:28.069 29427 29427 D NetworkSecurityConfig: No Network Security Config specified, using platform default
01-26 11:42:28.069 29427 29427 D NetworkSecurityConfig: No Network Security Config specified, using platform default
01-26 11:42:28.069 29427 29427 E ots.aidlservice: No implementation found for void crc6443da931ec5bafba4.MainApplication.n_onCreate() (tried Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate and Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate__) - is the library loaded, e.g. System.loadLibrary?
01-26 11:42:28.070 29427 29427 D AndroidRuntime: Shutting down VM
01-26 11:42:28.070 29427 29427 E AndroidRuntime: FATAL EXCEPTION: main
01-26 11:42:28.070 29427 29427 E AndroidRuntime: Process: com.stripe.cots.aidlservice, PID: 29427
01-26 11:42:28.070 29427 29427 E AndroidRuntime: java.lang.UnsatisfiedLinkError: No implementation found for void crc6443da931ec5bafba4.MainApplication.n_onCreate() (tried Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate and Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate__) - is the library loaded, e.g. System.loadLibrary?
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at crc6443da931ec5bafba4.MainApplication.n_onCreate(Native Method)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at crc6443da931ec5bafba4.MainApplication.onCreate(MainApplication.java:25)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1266)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6785)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2134)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:106)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.os.Looper.loopOnce(Looper.java:201)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:288)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:7898)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
01-26 11:42:28.070 29427 29427 E AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
01-26 11:42:28.071  1781 29443 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
01-26 11:42:28.071  1781  1858 I ActivityManager: Showing crash dialog for package com.bloomerang.stripe.demo u0

@microsoft-github-policy-service microsoft-github-policy-service bot added need-attention A xamarin-android contributor needs to review and removed need-info Issues that need more information from the author. labels Jan 26, 2024
@bloomer-joe
Copy link
Author

bloomer-joe commented Jan 26, 2024

@jonpryor , Update: I've been working through the steps you wrote. I am seeing crc6443da931ec5bafba4.MainApplication in obj/Release/net8.0-android/android/src/mono/android/app/ApplicationRegistration.java, just as you thought. I also verified that MonoRuntimeProvider was in my (generated) AndroidManifest.xml, hower there is a difference there. The value for android:authorities is

"com.bloomerang.stripe.demo.mono.MonoRuntimeProvider.mono_init"
not
"Mono.Android.NET_Tests.mono.MonoRuntimeProvider.mono_init"

That seems nominal though.

@bloomer-joe
Copy link
Author

Bump. I received approval to create a public repro. It may be a little while though.

@jasells
Copy link

jasells commented Feb 23, 2024

@bloomer-joe: a repro would also be handy.

@grendello , @jonpryor

I am a team-mate of @bloomer-joe and have been following the issue, and trying to resolve it as well.

I posted a repro-repo here and gave you access. I can add others or make it public if that helps.

@jasells
Copy link

jasells commented Feb 23, 2024

Here's a little more context from LogCat (before the error).

It seems to be the case that a service is trying to start, and that service is what cannot find our app's onCreate() binding, or native method.

java.lang.UnsatisfiedLinkError: No implementation found for void crc6443da931ec5bafba4.MainApplication.n_onCreate() 
(tried Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate and Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate__)
        	at crc6443da931ec5bafba4.MainApplication.n_onCreate(Native Method)
           	at crc6443da931ec5bafba4.MainApplication.onCreate(MainApplication.java:25)
           	at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1211)
           	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6715)
           	at android.app.ActivityThread.access$1300(ActivityThread.java:250)
           	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2045)
            at android.os.Handler.dispatchMessage(Handler.java:106)
            at android.os.Looper.loopOnce(Looper.java:201)
            at android.os.Looper.loop(Looper.java:288)
            at android.app.ActivityThread.main(ActivityThread.java:7829)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:982)
.
.
.
Time	Device Name	Type	PID	Tag	Message
	Google Pixel 4	Error	16074	AndroidRuntime	Process: com.stripe.cots.aidlservice, PID: 16074
.
.
.
Time	Device Name	Type	PID	Tag	Message
	Google Pixel 4	Error	16074	ots.aidlservice	No implementation found for void crc6443da931ec5bafba4.MainApplication.n_onCreate() (tried Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate and Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate__) - is the library loaded, e.g. System.loadLibrary?
.
.
.
Time	Device Name	Type	PID	Tag	Message
	Google Pixel 4	Info	1742	ActivityManager	Start proc 16074:com.stripe.cots.aidlservice/u0a365 for service {com.bloomerang.stripe.demo/com.stripe.cots.aidlservice.CotsService}

Note: I did open the .dex files generated from our build with Android Studio, and was able to find void crc6443da931ec5bafba4.MainApplication.n_onCreate() there, so not sure why it isn't found?

@grendello
Copy link
Contributor

@jasells thanks for the access and the repro! I wonder if the issue was fixed by recent Java.Interop changes in the main branch, @jonpryor what do you think?

@grendello
Copy link
Contributor

Specifically, I mean these changes: 25d1f00 and 06b1d7f

@jonpryor
Copy link
Member

@jasells: what do I need to do to repro the crash? Release config build of Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android launches without crashing for me, on a Pixel 6 Pro with Android 14. Do I need to use an emulator (which API level)? Do I need to accept all the permission prompts at startup?

% cd Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android
% dotnet build -c Release -f 
% adb install bin/Release/net7.0-android33.0/com.bloomerang.stripe.demo-Signed.apk
% adb shell am start com.bloomerang.stripe.demo/crc6443da931ec5bafba4.MainActivity

Immediately launches -- no apparent crash -- and prompts me to allow the app to access device location (don't allow), then prompts to allow the app to find the position of nearby devices (don't allow), then re-asked access to location (?!) (don't allow), then re-asks for permission for the position of nearby devices (don't allow), and then I see a screen.

How do I make this crash?

@jonpryor
Copy link
Member

@grendello asked:

I wonder if the issue was fixed by recent Java.Interop changes in the main branch, @jonpryor what do you think?
Specifically, I mean these changes: 25d1f00 and 06b1d7f

I suppose maybe? Probably more 06b1d7f.

However, as I can't repro the crash, I can neither confirm nor deny speculation. :-)

If it is 06b1d7f, then setting debug.mono.log to all should show both typemap & assembly load messages, and if we attempted to service a typemap before the assembly was loaded, that could indicate that 06b1d7f is in play.

But right now, we have no such indication.

@jonpryor
Copy link
Member

@bloomer-joe, @jasells: try this:

  1. adb shell setprop debug.mono.log all
  2. Start capturing logcat output: adb logcat > log.txt
  3. Run your app, get it to crash.

Then upload log.txt. In particular, I'd want all messages from the app, and from the crash logger for the app.

@jasells
Copy link

jasells commented Feb 27, 2024

Immediately launches -- no apparent crash -- and prompts me to allow the app to access device location (don't allow), then prompts to allow the app to find the position of nearby devices (don't allow), then re-asked access to location (?!) (don't allow), then re-asks for permission for the position of nearby devices (don't allow), and then I see a screen.

The crash (or, more precisely, the UnsatisfiedLinkError) occurs when attempting to enable/configure the NFC hardware, so by not allowing "position of nearby devices" you can't repro the issue, you must allow this permission, and in a RELEASE build.

After allowing all the requested permissions, Stripe.Terminal SDK will attempt to initialize and configure the NFC radio from a service, which is when Android will show the "app stopped responding" pop-up, and you can find the error in logcat.

I guess I may have hidden the Required Permissions in the Readme a little too far down...

@jasells
Copy link

jasells commented Feb 27, 2024

Then upload log.txt. In particular, I'd want all messages from the app, and from the crash logger for the app.

I can do that

@jasells
Copy link

jasells commented Feb 27, 2024

Here's a captured log with the settings requested.

(First install, with permission requests)
log.txt

(second run after permissions granted)
log2.txt

@jonpryor
Copy link
Member

@grendello: this feels unrelated to 06b1d7f, and more related to the fact that their service is in a separate process:

    <activity android:name="my.Activity.Name" android:exported="false" android:process="com.excample.process_name" android:theme="@style/Theme.CotsApp">
      <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
    </activity>
    <service android:name="some.external.ServiceType" android:exported="false" android:process="com.excample.process_name" />

Now that I know I need all the permissions -- my bad for not properly reading README.md -- I am able to repro the crash.

@jonpryor
Copy link
Member

@jasells: thank you for the logs, and the info for how to repro locally.

@jonpryor
Copy link
Member

@grendello: about my prior suggestion that it's related to android:process

From log2.txt, starting at line 7481:

02-27 16:34:30.006 29890 29916 I monodroid-gref: -g- grefc 355 gwrefc 0 handle 0x430a/G from thread '.NET ThreadPool Worker'(6)

We're getting messages from PID 29890. Lots of messages from PID 29890 (which is to be expected with debug.mono.log=all).

Skip ahead to the UnsatisfiedLinkError, line 11619:

02-27 16:34:47.874 30117 30117 E AndroidRuntime: java.lang.UnsatisfiedLinkError: No implementation found for void crc6443da931ec5bafba4.MainApplication.n_onCreate() (tried Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate and Java_crc6443da931ec5bafba4_MainApplication_n_1onCreate__) - is the library loaded, e.g. System.loadLibrary?

Different PID! This is 30117. What else came from 30117?

PID 30117 first appears on line 11561, and looking at lines that only pertain to PID 30117:

02-27 16:34:47.830  1067  1067 D Zygote  : Forked child process 30117
02-27 16:34:47.830  1742  1869 I ActivityManager: Start proc 30117:com.stripe.cots.aidlservice/u0a367 for service {com.bloomerang.stripe.demo/com.stripe.cots.aidlservice.CotsService}
02-27 16:34:47.844 30117 30117 I ots.aidlservice: Using CollectorTypeCC GC.
02-27 16:34:47.849 30117 30117 E ots.aidlservice: Not starting debugger since process cannot load the jdwp agent.
02-27 16:34:47.857 30117 30117 D CompatibilityChangeReporter: Compat change id reported: 171979766; UID 10367; state: ENABLED
02-27 16:34:47.863 30117 30117 W ziparchive: Unable to open '/data/app/~~J6boUmndnudbgfwBrMlFVA==/com.bloomerang.stripe.demo-ao9mzfLBQqHrbFMp2kTSMw==/split_config.arm64_v8a.dm': No such file or directory
02-27 16:34:47.863 30117 30117 W ziparchive: Unable to open '/data/app/~~J6boUmndnudbgfwBrMlFVA==/com.bloomerang.stripe.demo-ao9mzfLBQqHrbFMp2kTSMw==/split_config.arm64_v8a.dm': No such file or directory

…plus many more. However, of interest to me is that none of the messages from PID 30117 mention "monodroid". There is no equivalent to line 7481, or to line 19473:

02-27 16:43:07.380 30487 30487 I DOTNET  : JNI_OnLoad: JNI_OnLoad in pal_jni.c

(which is probably a different attempt to run the main process.)

In short, it doesn't look like the service process is invoking mono.MonoRuntimeProvider.

What's really interesting is that ManifestDocument.AddMonoRuntimeProviders() doesn't appear to have executed and/or done anything:

https://github.com/xamarin/xamarin-android/blob/43a397fa93b7d2ad4702c8a0b048844aefaa5fec/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs#L659-L693

What's also interesting is that we don't appear to have a unit test that uses ActivityAttribute.Process or ServiceAttribute.Process. We should probably fix that.

A(n) (untested) possibility is that the <service/> and <activity/> are added into the "real" AndroidManifest.xml after ManifestDocument does it's magic.

@jonpryor
Copy link
Member

@jasells, @bloomer-joe, @grendello:

On the assumption the <service/> and <activity/> elements with the android:process value are added after ManifestDocument runs, what if we force the issue?

[Activity(Label = "do not use", Exported = false, Process = "com.stripe.cots.aidlservice")]
public class JonpDummyActivity : Activity {
}

After rebuilding, this results in a obj/Release/net7.0-android33.0/android/manifest/AndroidManifest.xml which does declare MonoRuntimeProvider for the new process:

    <provider android:name="mono.MonoRuntimeProvider" android:authorities="com.bloomerang.stripe.demo.mono.MonoRuntimeProvider.__mono_init__" android:exported="false" android:initOrder="1999999999" />
    <provider android:name="mono.MonoRuntimeProvider_1" android:authorities="com.bloomerang.stripe.demo.mono.MonoRuntimeProvider_1.__mono_init__" android:exported="false" android:initOrder="1999999998" android:process="com.stripe.cots.aidlservice" />

Additionally, when I run this app locally, I no longer get UnsatisfiedLinkError!

That's the good news.

The bad news is that it still doesn't "work":

02-27 20:40:08.638 24474 24474 E AndroidRuntime: Process: com.stripe.cots.aidlservice, PID: 24474
02-27 20:40:08.638 24474 24474 E AndroidRuntime: java.lang.RuntimeException: Unable to bind to service com.stripe.cots.aidlservice.CotsService@7a6a4f0 with Intent { act= cmp=com.bloomerang.stri
pe.demo/com.stripe.cots.aidlservice.CotsService }: java.lang.ClassNotFoundException: com.google.android.play.core.integrity.IntegrityManagerFactory
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at android.app.ActivityThread.handleBindService(ActivityThread.java:4737)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at android.app.ActivityThread.-$$Nest$mhandleBindService(Unknown Source:0)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2297)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:106)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at android.os.Looper.loopOnce(Looper.java:205)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:294)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:8248)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
02-27 20:40:08.638 24474 24474 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: com.google.android.play.core.integrity.IntegrityManagerFactory
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at java.lang.Class.classForName(Native Method)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at java.lang.Class.forName(Class.java:536)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at java.lang.Class.forName(Class.java:467)
02-27 20:40:08.638 24474 24474 E AndroidRuntime:        at com.s.x.at$12884.Billing(:84)
…
02-27 20:40:08.649 24474 24474 W monodroid-assembly: Shared library 'liblog' not loaded, p/invoke '__android_log_print' may fail
02-27 20:40:08.654 24474 24474 I MonoDroid: UNHANDLED EXCEPTION:
02-27 20:40:08.674 24474 24474 I MonoDroid: Java.Lang.RuntimeException: Unable to bind to service com.stripe.cots.aidlservice.CotsService@7a6a4f0 with Intent { act= cmp=com.bloomerang.stripe.demo/com.stripe.cots.aidlservice.CotsService }: java.lang.ClassNotFoundException: com.google.android.play.core.integrity.IntegrityManagerFactory
02-27 20:40:08.674 24474 24474 I MonoDroid:  ---> Java.Lang.Exception: com.google.android.play.core.integrity.IntegrityManagerFactory
02-27 20:40:08.674 24474 24474 I MonoDroid: 
02-27 20:40:08.674 24474 24474 I MonoDroid:   --- End of managed Java.Lang.Exception stack trace ---
02-27 20:40:08.674 24474 24474 I MonoDroid: java.lang.ClassNotFoundException: com.google.android.play.core.integrity.IntegrityManagerFactory
…
02-27 20:40:08.675 24474 24474 I MonoDroid:   --- End of managed Java.Lang.Exception stack trace ---
02-27 20:40:08.675 24474 24474 I MonoDroid: java.lang.ClassNotFoundException: com.google.android.play.core.integrity.IntegrityManagerFactory
02-27 20:40:08.675 24474 24474 I MonoDroid:     at java.lang.Class.classForName(Native Method)
02-27 20:40:08.675 24474 24474 I MonoDroid:     at java.lang.Class.forName(Class.java:536)
02-27 20:40:08.675 24474 24474 I MonoDroid:     at java.lang.Class.forName(Class.java:467)
…
02-27 20:40:08.675 24474 24474 I MonoDroid:   --- End of managed Java.Lang.RuntimeException stack trace ---
02-27 20:40:08.675 24474 24474 I MonoDroid: java.lang.RuntimeException: Unable to bind to service com.stripe.cots.aidlservice.CotsService@7a6a4f0 with Intent { act= cmp=com.bloomerang.stripe.demo/com.stripe.cots.aidlservice.CotsService }: java.lang.ClassNotFoundException: com.google.android.play.core.integrity.IntegrityManagerFactory
…

…getting rather repetitive at this point.

The good news is that MonoDroid appears in the output, so we were definitely initialized now!

I just have no idea why com.google.android.play.core.integrity.IntegrityManagerFactory can't be found.

I can say that com.google.android.play.core.integrity.IntegrityManagerFactory is not present within classes.dex, so that explains why it isn't found, but I don't know or understand why it should be there in the first place.

Perhaps we just need a reference to Xamarin.Google.Android.Play.Integrity?

/me tries that…

@jonpryor
Copy link
Member

jonpryor commented Feb 28, 2024

@jasells, @bloomer-joe: @jonpryor suggested:

Perhaps we just need a reference to Xamarin.Google.Android.Play.Integrity?

After building it, classes.dex does contain com.google.android.play.core.integrity.IntegrityManagerFactory. After running it, my adb logcat no longer contains ClassNotFoundException.

It "works"…kinda.

Patch:

diff --git a/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/MainActivity.cs b/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/MainActivity.cs
index 7709385..6a51e35 100644
--- a/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/MainActivity.cs
+++ b/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/MainActivity.cs
@@ -88,4 +88,7 @@ namespace Stripe.Terminal.Demo.Droid
             //}
         }
     }
+    [Activity(Label = "do not use", Exported = false, Process = "com.stripe.cots.aidlservice")]
+    public class JonpDummyActivity : Activity {
+    }
 }
diff --git a/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/Stripe.Terminal.Demo.Android.csproj b/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/Stripe.Terminal.Demo.Android.csproj
index 86f145a..e1b7c5e 100644
--- a/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/Stripe.Terminal.Demo.Android.csproj
+++ b/Stripe.Terminal.Demo/Stripe.Terminal.Demo.Android/Stripe.Terminal.Demo.Android.csproj
@@ -71,6 +71,7 @@
     <PackageReference Include="Xamarin.Android.ReactiveX.RxJava3.RxKotlin" Version="3.0.1.8" />
     <PackageReference Include="Xamarin.Android.ReactiveX.RxKotlin" Version="2.4.0.8" />
 
+    <PackageReference Include="Xamarin.Google.Android.Play.Integrity " Version="1.2.0.3" />
     <PackageReference Include="Xamarin.Google.Guava" Version="32.0.1" />
     <PackageReference Include="Xamarin.AndroidX.DataBinding.ViewBinding" Version="8.0.0" />
 

However… It still crashes for me:

02-27 21:02:37.080 26780 26780 E AndroidRuntime: java.lang.OutOfMemoryError: Failed to allocate a 841101336 byte allocation with 25165824 free bytes and 249MB until OOM, target footprint 318924
48, growth limit 268435456
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at com.squareup.tape2.QueueFile$ElementIterator.next(QueueFile.java:549)
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at com.squareup.tape2.QueueFile$ElementIterator.next(QueueFile.java:514)
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at com.stripe.jvmcore.batchdispatcher.collectors.QueueFileCollector$peek$2.invokeSuspend(QueueFileCollector.kt:388)
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
02-27 21:02:37.080 26780 26780 E AndroidRuntime:        at java.lang.Thread.run(Thread.java:1012)

I am not debugging an OutOfMemoryError.

@jonpryor
Copy link
Member

@dellis1972: notwithstanding all of the (very verbose) above, one thing currently feels "off" about all this.

My previous comment noted the ClassNotFoundException for IntegrityManagerFactory.

What's funny is that it looks like it should have been there?

% jar tf obj/Release/net7.0-android33.0/lp/227/jl/classes.jar | grep IntegrityManagerFactory
com/google/android/play/core/integrity/IntegrityManagerFactory.class

The build tree contains a classes.jar, and as per obj/Release/net7.0-android33.0/lp/map.cache, lp/227 came from integrity.aar. It thus makes sense to me that it would contain IntegrityManagerFactory.

But IntegrityManagerFactory.class didn't make it into classes.dex until after I added Xamarin.Google.Android.Play.Integrity?.

Why is that? Is that a bug? My (incomplete) memory forgetting something?

@jonpryor
Copy link
Member

TL;DR, we have (at least?) three TODOs here:

  1. Update the build system so that we emit MonoRuntimeProvider entries after anything else has had a chance to update AndroidManifest.xml. This removes the need for JonpDummyActivity.
  2. Add a unit test that actually makes use of multiple processes.
  3. What's up with integrity.aar?

@jasells
Copy link

jasells commented Feb 28, 2024

For the record, we are seeing that OutOfMemoryError in our XForms build, after the NFC reader successfully connects, but only after we package the bindings into a nuget and use the nuget in our product app. The XForms build of this "demo" app is fine with project ref, or the nuget package...

It does seem to be something in the native Stripe libs for this version (2.23.3). And we've found this post that seems to discuss the issue in the tape2 lib. From that thread, it looks like there's some thread contention on a queue that is not thread safe?

It doesn't seem to be .Net/Mono related, unless it is a mis-matched nuget pulling in the wrong native lib for one of jar's? You guys have a better idea?

@dellis1972
Copy link
Contributor

@dellis1972: notwithstanding all of the (very verbose) above, one thing currently feels "off" about all this.

My previous comment noted the ClassNotFoundException for IntegrityManagerFactory.

What's funny is that it looks like it should have been there?

% jar tf obj/Release/net7.0-android33.0/lp/227/jl/classes.jar | grep IntegrityManagerFactory
com/google/android/play/core/integrity/IntegrityManagerFactory.class

The build tree contains a classes.jar, and as per obj/Release/net7.0-android33.0/lp/map.cache, lp/227 came from integrity.aar. It thus makes sense to me that it would contain IntegrityManagerFactory.

But IntegrityManagerFactory.class didn't make it into classes.dex until after I added Xamarin.Google.Android.Play.Integrity?.

Why is that? Is that a bug? My (incomplete) memory forgetting something?

Could it be R8 is stripping the class out?

@grendello
Copy link
Contributor

@jonpryor one problem I remember from years ago with regards to monodroid in a separate process was that our runtime was never initialized in that process, only in the "main" process. If the separate process, however, was forked off of the main one after the latter was initialized, everything worked fine. But, if the isolated process was somehow initialized before we finished initializing our runtime (I think mostly in the narrow window between when the OS calls our Java startup sequence and we load libmonodroid.so), then things would mostly crash.

@jonpryor
Copy link
Member

@grendello: indeed, we don't -- can't, as far as I currently know -- support android:isolatedProcess. That's tracked at #3378.

@dellis1972
Copy link
Contributor

@jonpryor so if the code which uses the IntegrityManagerFactory is only called by the other process, I'm not sure what R8 would do in that case. Its probably being stripped out maybe? I can't see any reason why it would not be included in the compilation step.

@jasells
Copy link

jasells commented Feb 28, 2024

If the separate process, however, was forked off of the main one after the latter was initialized, everything worked fine. But, if the isolated process was somehow initialized before we finished initializing our runtime (I think mostly in the narrow window between when the OS calls our Java startup sequence and we load libmonodroid.so), then things would mostly crash.

Just throwing this out there: com.stripe.cots.aidlservice is defined in stripeterminal-internal-common-2.23.3.aar. In the Stripe.Terminal.Binding.Android.Internal.Common project, I did change the metadata in the project file, since we don't directly call into it from application-code. The new bind settings:
<LibraryProjectZip Include="Jars\stripeterminal-internal-common-2.23.3.aar" Bind="false"/>

In the previous version of our code (XForms) using an older version of the native SDK (2.20.0) I used the custom BindingTool I wrote to exclude all the native types from C# binding generation step by emitting Metadata.xml, but perhaps that would force/allowe it to get registered correctly so that the service-process could access the mono runtime and/or only after mono-init?

-trying that now

@jasells
Copy link

jasells commented Feb 28, 2024

Just throwing this out there: com.stripe.cots.aidlservice is defined in stripeterminal-internal-common-2.23.3.aar.

Sratch all that. I misread the api.xml file. There are other "services" in the com.stripe.cots.aidlservice namespace in the stripeterminal-internal-common-2.23.3.aar, but not the CotsService that is source of the unsatisfied link error specifically.

@jasells
Copy link

jasells commented Feb 28, 2024

@jonpryor
OK, so I took the patch you posted in this thread, and I am also seeing the UnsatisfiedLinkError issue resolved. I can connect to the NFC hardware, and successfully process a Tap-to-pay transaction with a test card! 🎉

TY!

I pushed a new branch with the patch applied.

re: OOM error.

The first build after applying the patch I also saw the OOM error and crash, though after connecting the NFC hardware successfully.

After manually deleting all /obj and /bin directories in the repo, removing the app from the device, and redeploying, the OOM error has not shown again, even after allowing the app to run for > 30 minutes multiple times. Not sure if the rebuild/redeploy actually had anything to do with the OOM disappearance.

I'm not asking for you to debug that, just sharing in case it rings a bell, or others find this thread. See this comment for more details on the OOM.

@jonpryor
Copy link
Member

jonpryor commented Mar 4, 2024

@dellis1972 suggested:

Could it be R8 is stripping the class out?

Maybe? However, when I try to provide a ProGuard.txt file to preserve those types, they're still not preserved. I'm not sure what I'm doing wrong.

@jpobst jpobst removed the need-attention A xamarin-android contributor needs to review label Mar 20, 2024
@ciuffo81
Copy link

ciuffo81 commented Sep 5, 2024

@bloomer-joe: a repro would also be handy.

@grendello , @jonpryor

I am a team-mate of @bloomer-joe and have been following the issue, and trying to resolve it as well.

I posted a repro-repo here and gave you access. I can add others or make it public if that helps.

@jasells
Could you give me the access to the repo? Please
I have the same issue.

@jasells
Copy link

jasells commented Sep 5, 2024

re: @ciuffo81

@jasells Could you give me the access to the repo? Please I have the same issue.

I'm not sure what your issue is, but odds are > 90% not this issue if you have an unsatisfied link error. Odds are you're simply missing a package. However, if you have a similar situation where you have a native Android service launching from a package lib, then the following snippet to a new activity that essentially registers the service with app-code (or vice-versa) so it can call back was the solution. You'll need to change the process name to match your situation unless you're also dealing with Stripe.Terminal SDK.

  [Activity(Label = "do not use", Exported = false, Process = "com.stripe.cots.aidlservice")]
   public class JonpDummyActivity : Activity { }

@ivantespass
Copy link

ivantespass commented Sep 5, 2024

re: @ciuffo81

@jasells Could you give me the access to the repo? Please I have the same issue.

I'm not sure what your issue is, but odds are > 90% not this issue if you have an unsatisfied link error. Odds are you're simply missing a package. However, if you have a similar situation where you have a native Android service launching from a package lib, then the following snippet to a new activity that essentially registers the service with app-code (or vice-versa) so it can call back was the solution. You'll need to change the process name to match your situation unless you're also dealing with Stripe.Terminal SDK.

  [Activity(Label = "do not use", Exported = false, Process = "com.stripe.cots.aidlservice")]
   public class JonpDummyActivity : Activity { }

Hi @jasells, thanks for your quick reply.

I am a @ciuffo81 team-mate: we are facing the same identical issue with the same callstack regarding Stripe.Terminal SDK so far, we are trying to develop an integration of TerminalSDK (MAUI).

Even with snippet provided we are not able to resolve the problem.

It would be useful to analyze your repo to compare your solution with our issue.

Thank you in advance.

@jasells
Copy link

jasells commented Sep 5, 2024

@ivantespass, @ciuffo81

I recommend checking out this repo, as maintaining the bindings are quite a task (Stripe has a 2-release per month cadence):
https://github.com/Envoc/envoc-stripe-binding

I have a few comments on one of the issue threads here, but I haven't had time to get more familiar with this repo. However, trying to retire the private binding repo referenced here and use envoc-stripe-binding is certainly something I am considering long term for the project I am supporting.

The Stripe.Terminal binding repo referenced in this thread is a private repo that I do not own. It is owned by my employer, and I don't have permission at this time to release it publicly. I am only maintaining that private repo because I inherited it when I took this job, and it is going to be a major effort to replace it at this point, and we're in the middle of porting the app that uses it from XForms to Maui. If I were just now adding Stripe.Terminal support to an app as a new feature, I would at least try to use the envoc-stripe-binding before trying to build my own bindings, since as you likely know, the Terminal-Android SDK is fairly complex, as often happens in java projects 😒. What I can tell you is that the repro-repo referenced is a multi-project Maui solution: i.e. it has an Android and an iOS project, structed like a typical XForms app solution, not the typical single-project Maui (shared code) solution used in most examples on MSDN, etc, and may be related to the issue? This template package may be of use if you want to pursue setting up a Maui-multiproject solution.

Some of the dependencies and build tools used in the private terminal binding repo are now public, and contributions are welcomed, if you are determined to build your own version of Stripe.Terminal for .NET. My long - term aim would be to contribute to the envoc-stripe-binding by referencing some or all of the dependencies here:
https://xdevapps.visualstudio.com/DefaultCollection/Android%20bindings once I and my team are finished with our Maui - port, which is stills a few months out.

All the packages produced by repos in that project are available on Nuget.Org, and I do expect at least one more update/upgrade of my private bindings from Stripe.Terminal v2.23.3 to v3.x before switching to the Envoc bindings, unless that turns out to be much more work than I expect. That would mean updating most or all of those dependency repos, I'm just not sure what versions.

@jasells
Copy link

jasells commented Sep 5, 2024

@ivantespass, @ciuffo81

if your binding repo is public... I might have a few minutes here or there to take a look and see if I can help...

I would be interested in other collaborative options to maintain a public Stripe-terminal binding package, if the Envoc repo owner is not receptive to collaboration/contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: App Runtime Issues in `libmonodroid.so`.
Projects
None yet
Development

No branches or pull requests

8 participants