learntoflutter / flutter_embed_unity

19 stars 4 forks source link

Android <9 crash - no non-static method UnityPlayerSingleton;.hidePreservedContent() #15

Closed timbotimbo closed 7 months ago

timbotimbo commented 8 months ago

Description

I'm reposting this issue because it affects this plugin too.

On some Unity versions, android devices on Android 8 or lower will immediately crash when Unity is started.

java.lang.NoSuchMethodError: no non-static method "Lcom/learntoflutter/flutter_embed_unity_android/unity/UnityPlayerSingleton;.hidePreservedContent()V"

Breaking change

I tested multiple Unity versions to find the exact version that breaks this.

The crash starts at Unity 2021.3.31 and 2022.3.10.
Both these versions have this entry in the changelog, that seems like the culprit.

  • Android: Show the last rendered frame when the application come back from the pause state. (UUM-30881)

As of 2022.3.19 this crash no longer happens, but there is nothing mentioned in the Unity changelog.

It's your choice to either add a workaround that might become redundant or tell people to upgrade to 2022.3.19+

Reproduction

  1. Build and run any project using this plugin, like the example.
  2. Wait until Unity launches in Flutter app.
  3. On Android <= 8 and the specified Unity versions, it will instantly crash.

Workaround

For affected versions, you can avoid the crash by defining the missing function and calling the private function using reflection.
This fixes the crash on Android <= 8 and doesn't change any behavior on higher android versions.

I haven't benchmarked this, but it might introduce a some milliseconds delay on Android 9+ as you add a reflection call that isn't otherwise needed.

import java.lang.reflect.Method

     fun hidePreservedContent() {

        // manually call the private function using reflection.
        UnityPlayer::class.java.declaredMethods
        .find { it.name == "hidePreservedContent" }
        ?.let {
            it.isAccessible = true
            it.invoke(this)
        }

     }

Versions

❌ indicates a crash.

Unity: 2022.3.0 ✔
2022.3.9 ✔
2022.3.10 ❌
2022.3.14 ❌ 2022.3.15 ❌ 2022.3.18 ❌ 2022.3.19 ✔ 2022.3.20 ✔

Android: Android 6 ❌
Android 7.1.2 ❌
Android 8 ❌ (reported) Android 9 ✔ Android 13 ✔

Devices: This seems to happen on any device on Android <= 8.

Logs

Log Unity 2022.3.18, Android 7 ``` E/AndroidRuntime( 5807): FATAL EXCEPTION: UnityMain E/AndroidRuntime( 5807): Process: com.learntoflutter.flutter_embed_unity_example, PID: 5807 E/AndroidRuntime( 5807): java.lang.Error: FATAL EXCEPTION [UnityMain] E/AndroidRuntime( 5807): Unity version : 2022.3.18f1 E/AndroidRuntime( 5807): Device model : Wileyfox Wileyfox Swift E/AndroidRuntime( 5807): Device fingerprint: Wileyfox/Swift/crackling:7.1.2/N2G48B/eeea277114:user/release-keys E/AndroidRuntime( 5807): CPU supported ABI : [arm64-v8a, armeabi-v7a, armeabi] E/AndroidRuntime( 5807): Build Type : Release E/AndroidRuntime( 5807): Scripting Backend : IL2CPP E/AndroidRuntime( 5807): Libs loaded from : lib/arm64 E/AndroidRuntime( 5807): Strip Engine Code : true E/AndroidRuntime( 5807): E/AndroidRuntime( 5807): Caused by: java.lang.NoSuchMethodError: no non-static method "Lcom/learntoflutter/flutter_embed_unity_android/unity/UnityPlayerSingleton;.hidePreservedContent()V" E/AndroidRuntime( 5807): at com.unity3d.player.UnityPlayer.nativeRender(Native Method) E/AndroidRuntime( 5807): at com.unity3d.player.UnityPlayer.-$$Nest$mnativeRender(Unknown Source) E/AndroidRuntime( 5807): at com.unity3d.player.UnityPlayer$F$a.handleMessage(Unknown Source) E/AndroidRuntime( 5807): at android.os.Handler.dispatchMessage(Handler.java:98) E/AndroidRuntime( 5807): at android.os.Looper.loop(Looper.java:154) E/AndroidRuntime( 5807): at com.unity3d.player.UnityPlayer$F.run(Unknown Source) ```
jamesncl commented 7 months ago

Thanks for your usual high quality bug report!

I've just pushed your workaround to a new branch bug/issue15 with a conditional check so it only runs the reflection if it's <Android 9. I don't have any Android 8 devices to test this with, would appreciate if you could check this fixes the issue (I've also updated the dependencies to point to local paths, so you should just be able to run the example project in the android sub-package)

timbotimbo commented 7 months ago

The issue15 branch fixes the crash on my Android 7 device, but actually breaks on Android >= 9.

https://github.com/learntoflutter/flutter_embed_unity/assets/11444698/41b28045-6086-473f-8c9c-8216905203c4

Ideally you would want to do conditional compilation like possible in C#, but is seems like kotlin doesn't support this.

#if android < 9
 fun hidePreservedContent() { ... }
#endif

I've also seen someone on the flutter_unity_widget discord having freezing issues on resume (like in my video), that were resolved once he upgraded from 2022.3.16 to 2022.3.20.
This sounds like it is caused by the same issue except that his app didn't crash.

He got the issue on an Android 8 phone and Android 11 emulator.

So there might be more devices affected in a different way, but I haven't been able to reproduce that yet.

jamesncl commented 7 months ago

Thanks for the update - I found an old Motorola with Android 5.1 and I can now reproduce the issue, and I see what you mean about it breaking on Android > 8.

As far as I know conditional compilation based on Android SDK isn't possible because at compile time you don't know the version of Android, it has to be a runtime check.

So it looks like the options are:

  1. always use reflection regardless of SDK version. I agree it's not ideal, but also not the end of the world - I imagine this function isn't called a lot. It might be an acceptable tradeoff for stability for users
  2. figure out what this function actually does and replicate it

For 2, I took a copy of /Applications/Unity/Hub/Editor/2022.3.20f1/PlaybackEngines/AndroidPlayer/Variations/mono/Development/Classes/classes.jar, unzipped, and opened in Android Studio (it decompiles class files). The decompiled version of the function in UnityPlayer.class is very simple, it is just this:

    private void hidePreservedContent() {
        c var1;
        var1 = new c.<init>(this);
        this.runOnUiThread(var1);
    }

I checked Unity 2022.3.9, this function does not exist. It does exist in 2022.3.13 and 2022.3.20, and is the same in both.

The only problem is I can't figure out what c might refer to. Something that you create, pass UnityPlayer instance to it, and run it. If anyone who is better than me at reading decompiled code can figure out what this is, we can probably just replicate the function and avoid using reflection. On the other hand, it may make the package more brittle to changes in future versions of Unity (i.e. if this function changes), so maybe refection is the better choice anyway?

Full UnityPlayer.class ```java // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.unity3d.player; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.view.InputEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.OrientationEventListener; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import com.unity3d.player.C.a; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class UnityPlayer extends FrameLayout implements IUnityPlayerLifecycleEvents { public static Activity currentActivity; public static Context currentContext; private static final int RUN_STATE_CHANGED_MSG_CODE = 2269; private static final String SPLASH_ENABLE_METADATA_NAME = "unity.splash-enable"; private static final String SPLASH_MODE_METADATA_NAME = "unity.splash-mode"; private static final String LAUNCH_FULLSCREEN = "unity.launch-fullscreen"; private static final String ARCORE_ENABLE_METADATA_NAME = "unity.arcore-enable"; private static final String AUTO_REPORT_FULLY_DRAWN_ENABLE_METADATA_NAME = "unity.auto-report-fully-drawn"; Handler mHandler; private int mInitialScreenOrientation; private boolean mMainDisplayOverride; private boolean mIsFullscreen; private J mState; private final ConcurrentLinkedQueue m_Events; private OrientationEventListener mOrientationListener; private int mNaturalOrientation; private Configuration prevConfig; F m_MainThread; private boolean m_AddPhoneCallListener; private D m_PhoneCallListener; private TelephonyManager m_TelephonyManager; private ClipboardManager m_ClipboardManager; private com.unity3d.player.C m_SplashScreen; private GoogleARCoreApi m_ARCoreApi; private B m_FakeListener; private Camera2Wrapper m_Camera2Wrapper; private HFPStatus m_HFPStatus; private AudioVolumeHandler m_AudioVolumeHandler; private OrientationLockListener m_OrientationLockListener; private Uri m_launchUri; private NetworkConnectivity m_NetworkConnectivity; private IUnityPlayerLifecycleEvents m_UnityPlayerLifecycleEvents; private int m_IsNoWindowMode; Window m_Window; private Context mContext; private Activity mActivity; private I mGlView; private boolean mQuitting; private boolean mProcessKillRequested; private U mVideoPlayerProxy; com.unity3d.player.B mSoftInputDialog; public UnityPlayer(Context var1) { this(var1, (IUnityPlayerLifecycleEvents)null); } public UnityPlayer(Context var1, IUnityPlayerLifecycleEvents var2) { super(var1); Handler var3; var3 = new Handler.(); this.mHandler = var3; this.mInitialScreenOrientation = -1; this.mMainDisplayOverride = false; this.mIsFullscreen = true; J var7; var7 = new J.(); this.mState = var7; ConcurrentLinkedQueue var9; var9 = new ConcurrentLinkedQueue.(); this.m_Events = var9; this.mOrientationListener = null; F var11; var11 = new F.(); this.m_MainThread = var11; this.m_AddPhoneCallListener = false; D var13; var13 = new D.(); this.m_PhoneCallListener = var13; this.m_ARCoreApi = null; B var14; var14 = new B.(this); this.m_FakeListener = var14; this.m_Camera2Wrapper = null; this.m_HFPStatus = null; this.m_AudioVolumeHandler = null; this.m_OrientationLockListener = null; this.m_launchUri = null; this.m_NetworkConnectivity = null; this.m_UnityPlayerLifecycleEvents = null; this.m_IsNoWindowMode = -1; this.mProcessKillRequested = true; this.mSoftInputDialog = null; if (var2 == null) { var2 = this; } this.m_UnityPlayerLifecycleEvents = (IUnityPlayerLifecycleEvents)var2; com.unity3d.player.G.a(getUnityNativeLibraryPath(var1)); currentContext = var1; if (var1 instanceof Activity) { Activity var10003 = (Activity)var1; currentActivity = this.mActivity = (Activity)var1; this.mInitialScreenOrientation = var10003.getRequestedOrientation(); this.m_launchUri = this.mActivity.getIntent().getData(); } this.mContext = var1; this.EarlyEnableFullScreenIfEnabled(); Configuration var6; this.prevConfig = var6 = this.getResources().getConfiguration(); this.mNaturalOrientation = this.getNaturalOrientation(var6.orientation); if (this.mActivity != null && this.getSplashEnabled()) { com.unity3d.player.C var8; com.unity3d.player.C var10002 = var8 = new com.unity3d.player.C; var10002.(this.mContext, a.a()[this.getSplashMode()]); this.m_SplashScreen = var10002; this.addView(var8); } preloadJavaPlugins(); String var10 = loadNative(getUnityNativeLibraryPath(this.mContext)); if (!J.d()) { u.Log(6, "Your hardware does not support this application."); AlertDialog.Builder var15 = (new AlertDialog.Builder(this.mContext)).setTitle("Failure to initialize!"); DialogInterface.OnClickListener var5; var5 = new DialogInterface.OnClickListener() { public void onClick(DialogInterface var1, int var2) { UnityPlayer.this.finish(); } }.(); AlertDialog var16 = var15.setPositiveButton("OK", var5).setMessage("Your hardware does not support this application." + "\n\n" + var10 + "\n\n Press OK to quit.").create(); var16.setCancelable(false); var16.show(); } else { this.initJni(var1); this.mState.d(true); I var12; I var10006 = var12 = new I; var10006.(var1, this); this.mGlView = var10006; this.addView(var12); this.bringChildToFront(this.m_SplashScreen); this.mQuitting = false; Activity var4; if ((var4 = this.mActivity) != null) { this.m_Window = var4.getWindow(); } if ((var4 = this.mActivity) != null) { Window var10000 = var4.getWindow(); var10000.addFlags(2097152); var10000.addFlags(524288); var10000.addFlags(4194304); } this.hideStatusBar(); this.m_TelephonyManager = (TelephonyManager)this.mContext.getSystemService("phone"); this.m_ClipboardManager = (ClipboardManager)this.mContext.getSystemService("clipboard"); this.m_Camera2Wrapper = new Camera2Wrapper(this.mContext); this.m_HFPStatus = new HFPStatus(this.mContext); this.m_MainThread.start(); } } private int getNaturalOrientation(int var1) { int var2; return ((var2 = ((WindowManager)this.mContext.getSystemService("window")).getDefaultDisplay().getRotation()) == 0 || var2 == 2) && var1 == 2 || (var2 == 1 || var2 == 3) && var1 == 1 ? 0 : 1; } private String GetGlViewContentDescription(Context var1) { Resources var10000 = var1.getResources(); Resources var10001 = var1.getResources(); String var2 = var1.getPackageName(); return var10000.getString(var10001.getIdentifier("game_view_content_description", "string", var2)); } private void DisableStaticSplashScreen() { Runnable var1; var1 = new Runnable() { public void run() { UnityPlayer var10001 = UnityPlayer.this; var10001.removeView(var10001.m_SplashScreen); UnityPlayer.this.m_SplashScreen = null; } }.(); this.runOnUiThread(var1); } private void EarlyEnableFullScreenIfEnabled() { Activity var1; View var2; if ((var1 = this.mActivity) != null && var1.getWindow() != null && (this.getLaunchFullscreen() || this.mActivity.getIntent().getBooleanExtra("android.intent.extra.VR_LAUNCH", false)) && (var2 = this.mActivity.getWindow().getDecorView()) != null) { var2.setSystemUiVisibility(7); } } private boolean IsWindowTranslucent() { Activity var1; if ((var1 = this.mActivity) == null) { return false; } else { TypedArray var2; boolean var10000 = (var2 = var1.getTheme().obtainStyledAttributes(new int[]{16842840})).getBoolean(0, false); var2.recycle(); return var10000; } } private boolean updateDisplayInternal(int var1, Surface var2) { if (J.d() && this.mState.a()) { Semaphore var3; var3 = new Semaphore.(0); Runnable var4; var4 = new Runnable() { { this.a = var2; this.b = var3; this.c = var4; } public void run() { var10000 = this; UnityPlayer var10001 = UnityPlayer.this; var10002 = this; int var1 = this.a; var10001.nativeRecreateGfxState(var1, var10002.b); var10000.c.release(); } }.(var1, var2, var3); if (var1 == 0) { Handler var5; F var10; if (var2 == null) { if ((var5 = (var10 = this.m_MainThread).a) != null) { Message.obtain(var5, 2269, UnityPlayer.E.d).sendToTarget(); Message.obtain(var10.a, var4).sendToTarget(); } } else if ((var5 = (var10 = this.m_MainThread).a) != null) { F var10000 = var10; Message.obtain(var5, var4).sendToTarget(); E var11 = UnityPlayer.E.e; Handler var13; if ((var13 = var10000.a) != null) { Message.obtain(var13, 2269, var11).sendToTarget(); } } } else { var4.run(); } if (var2 == null && var1 == 0) { label70: { Semaphore var14; SynchronizationTimeout var17; boolean var10001; try { var14 = var3; var17 = UnityPlayer.SynchronizationTimeout.SurfaceDetach; } catch (InterruptedException var9) { var10001 = false; break label70; } SynchronizationTimeout var12 = var17; int var18; try { var18 = var17.getTimeout(); } catch (InterruptedException var8) { var10001 = false; break label70; } long var19 = (long)var18; boolean var15; try { var15 = var14.tryAcquire(var19, TimeUnit.MILLISECONDS); } catch (InterruptedException var7) { var10001 = false; break label70; } if (var15) { return true; } byte var16 = 5; try { u.Log(var16, "Timeout (" + var12.getTimeout() + " ms) while trying detaching primary window."); return true; } catch (InterruptedException var6) { var10001 = false; } } u.Log(5, "UI thread got interrupted while trying to detach the primary window from the Unity Engine."); } return true; } else { return false; } } public static void UnitySendMessage(String var0, String var1, String var2) { if (!J.d()) { u.Log(5, "Native libraries not loaded - dropping message for " + var0 + "." + var1); } else { try { nativeUnitySendMessage(var0, var1, var2.getBytes("UTF-8")); } catch (UnsupportedEncodingException var3) { } } } private static native void nativeUnitySendMessage(String var0, String var1, byte[] var2); private void finish() { Activity var1; if ((var1 = this.mActivity) != null && !var1.isFinishing()) { this.mActivity.finish(); } } private void queueDestroy() { u.Log(4, "Queue Destroy"); Runnable var1; var1 = new Runnable() { public void run() { UnityPlayer.this.destroy(); } }.(); this.runOnUiThread(var1); } private void pauseUnity() { this.reportSoftInputStr((String)null, 1, true); if (this.mState.c() && !this.mState.b()) { if (J.d()) { label66: { Semaphore var1; var1 = new Semaphore.(0); Runnable var2; if (this.isFinishing()) { var2 = new Runnable() { { this.a = var2; } public void run() { UnityPlayer.this.shutdown(); this.a.release(); } }.(var1); } else { var2 = new Runnable() { { this.a = var2; } public void run() { if (UnityPlayer.this.nativePause()) { UnityPlayer var10001 = UnityPlayer.this; var10001.mQuitting = true; var10001.shutdown(); UnityPlayer.this.queueDestroy(); } this.a.release(); } }.(var1); } F var3; Handler var4; if ((var4 = (var3 = this.m_MainThread).a) != null) { Message.obtain(var4, 2269, UnityPlayer.E.a).sendToTarget(); Message.obtain(var3.a, var2).sendToTarget(); } label67: { SynchronizationTimeout var12; Semaphore var10000; boolean var10001; try { var10000 = var1; var12 = UnityPlayer.SynchronizationTimeout.Pause; } catch (InterruptedException var8) { var10001 = false; break label67; } SynchronizationTimeout var9 = var12; int var13; try { var13 = var12.getTimeout(); } catch (InterruptedException var7) { var10001 = false; break label67; } long var14 = (long)var13; boolean var10; try { var10 = var10000.tryAcquire(var14, TimeUnit.MILLISECONDS); } catch (InterruptedException var6) { var10001 = false; break label67; } if (var10) { break label66; } byte var11 = 5; try { u.Log(var11, "Timeout (" + var9.getTimeout() + " ms) while trying to pause the Unity Engine."); break label66; } catch (InterruptedException var5) { var10001 = false; } } u.Log(5, "UI thread got interrupted while trying to pause the Unity Engine."); } } this.mState.c(false); this.mState.e(true); if (this.m_AddPhoneCallListener) { this.m_TelephonyManager.listen(this.m_PhoneCallListener, 0); } } } private void shutdown() { this.mProcessKillRequested = this.nativeDone(); this.mState.d(false); } private void checkResumePlayer() { boolean var1 = false; Activity var2; if ((var2 = this.mActivity) != null) { var1 = MultiWindowSupport.isInMultiWindowMode(var2); } if (this.mState.a(var1)) { this.mState.c(true); this.queueGLThreadEvent((Runnable)(new a(this))); F var10000 = this.m_MainThread; E var3 = UnityPlayer.E.b; Handler var4; if ((var4 = var10000.a) != null) { Message.obtain(var4, 2269, var3).sendToTarget(); } } } private final native void initJni(Context var1); private final native boolean nativeRender(); private final native void nativeSetInputArea(int var1, int var2, int var3, int var4); private final native void nativeSetKeyboardIsVisible(boolean var1); private final native void nativeSetInputString(String var1); private final native void nativeSetInputSelection(int var1, int var2); private final native void nativeSoftInputCanceled(); private final native void nativeSoftInputLostFocus(); private final native void nativeReportKeyboardConfigChanged(); private final native boolean nativePause(); private final native void nativeResume(); private final native void nativeLowMemory(); private final native void nativeApplicationUnload(); private final native void nativeFocusChanged(boolean var1); private final native void nativeRecreateGfxState(int var1, Surface var2); private final native void nativeSendSurfaceChangedEvent(); private final native boolean nativeDone(); private final native void nativeSoftInputClosed(); private final native boolean nativeInjectEvent(InputEvent var1); private final native boolean nativeIsAutorotationOn(); private final native void nativeMuteMasterAudio(boolean var1); private final native void nativeRestartActivityIndicator(); private final native void nativeSetLaunchURL(String var1); private final native void nativeOrientationChanged(int var1, int var2); private final native boolean nativeGetNoWindowMode(); private final native void nativeHidePreservedContent(); private static String logLoadLibMainError(String var0, String var1) { String var10000 = "Failed to load 'libmain.so'\n\n" + var1; u.Log(6, var10000); return var10000; } private static void preloadJavaPlugins() { try { Class.forName("com.unity3d.JavaPluginPreloader"); } catch (ClassNotFoundException var1) { } catch (LinkageError var2) { u.Log(6, "Java class preloading failed: " + var2.getMessage()); } } private static String loadNative(String param0) { // $FF: Couldn't be decompiled } private static void unloadNative() { if (J.d()) { if (NativeLoader.unload()) { J.f(); } else { throw new UnsatisfiedLinkError("Unable to unload libraries from libmain.so"); } } } private static String getUnityNativeLibraryPath(Context param0) { // $FF: Couldn't be decompiled } private void hidePreservedContent() { c var1; var1 = new c.(this); this.runOnUiThread(var1); } private String getProcessName() { UnityPlayer var10000 = this; int var3 = Process.myPid(); List var1; if ((var1 = ((ActivityManager)var10000.mContext.getSystemService("activity")).getRunningAppProcesses()) == null) { return null; } else { Iterator var4 = var1.iterator(); ActivityManager.RunningAppProcessInfo var2; do { if (!var4.hasNext()) { return null; } } while((var2 = (ActivityManager.RunningAppProcessInfo)var4.next()).pid != var3); return var2.processName; } } private ApplicationInfo getApplicationInfo() { return this.mContext.getPackageManager().getApplicationInfo(this.mContext.getPackageName(), 128); } private ActivityInfo getActivityInfo() { return this.mActivity.getPackageManager().getActivityInfo(this.mActivity.getComponentName(), 128); } private boolean getSplashEnabled() { try { return this.getApplicationInfo().metaData.getBoolean("unity.splash-enable"); } catch (Exception var1) { return false; } } private boolean getARCoreEnabled() { try { return this.getApplicationInfo().metaData.getBoolean("unity.arcore-enable"); } catch (Exception var1) { return false; } } private boolean getLaunchFullscreen() { try { return this.getApplicationInfo().metaData.getBoolean("unity.launch-fullscreen"); } catch (Exception var1) { return false; } } private boolean getHaveAndroidWindowSupport() { if (this.m_IsNoWindowMode == -1) { this.m_IsNoWindowMode = this.nativeGetNoWindowMode(); } return this.m_IsNoWindowMode == 1; } private boolean getAutoReportFullyDrawnEnabled() { try { return this.getApplicationInfo().metaData.getBoolean("unity.auto-report-fully-drawn"); } catch (Exception var1) { return false; } } private void queueGLThreadEvent(G var1) { if (!this.isFinishing()) { this.queueGLThreadEvent((Runnable)var1); } } private void hideStatusBar() { Activity var1; if ((var1 = this.mActivity) != null) { var1.getWindow().setFlags(1024, 1024); } } private void swapViews(View var1, View var2) { boolean var3 = false; if (!this.mState.b()) { this.pause(); var3 = true; } ViewParent var4; if (var1 != null && (!((var4 = var1.getParent()) instanceof UnityPlayer) || (UnityPlayer)var4 != this)) { if (var4 instanceof ViewGroup) { ((ViewGroup)var4).removeView(var1); } this.addView(var1); this.bringChildToFront(var1); var1.setVisibility(0); } if (var2 != null && var2.getParent() == this) { var2.setVisibility(8); this.removeView(var2); } if (var3) { this.resume(); } } static { (new com.unity3d.player.G()).a(); } public void onUnityPlayerUnloaded() { u.Log(4, "onUnityPlayerUnloaded, pass IUnityPlayerLifecycleEvents to UnityPlayer constructor or override this method in child class"); } public void onUnityPlayerQuitted() { u.Log(4, "onUnityPlayerQuitted, pass IUnityPlayerLifecycleEvents to UnityPlayer constructor or override this method in child class"); } protected void toggleGyroscopeSensor(boolean var1) { boolean var10000 = var1; SensorManager var3; Sensor var2 = (var3 = (SensorManager)this.mContext.getSystemService("sensor")).getDefaultSensor(11); if (var10000) { var3.registerListener(this.m_FakeListener, var2, 1); } else { var3.unregisterListener(this.m_FakeListener); } } void sendSurfaceChangedEvent() { if (J.d() && this.mState.a()) { Runnable var1; var1 = new Runnable() { public void run() { UnityPlayer.this.nativeSendSurfaceChangedEvent(); } }.(); Handler var2; if ((var2 = this.m_MainThread.a) != null) { Message.obtain(var2, var1).sendToTarget(); } } } void updateGLDisplay(int var1, Surface var2) { if (!this.mMainDisplayOverride) { this.updateDisplayInternal(var1, var2); } } public boolean displayChanged(int var1, Surface var2) { if (var1 == 0) { boolean var3; if (var2 != null) { var3 = true; } else { var3 = false; } this.mMainDisplayOverride = var3; Runnable var4; var4 = new Runnable() { public void run() { UnityPlayer var1; if ((var1 = UnityPlayer.this).mMainDisplayOverride) { var1.removeView(var1.mGlView); } else if (var1.mGlView.getParent() == null) { UnityPlayer var10000 = UnityPlayer.this; var10000.addView(var10000.mGlView); } else { u.Log(5, "Couldn't add view, because it's already assigned to another parent"); } } }.(); this.runOnUiThread(var4); } return this.updateDisplayInternal(var1, var2); } void runOnAnonymousThread(Runnable var1) { (new Thread(var1)).start(); } void runOnUiThread(Runnable var1) { Activity var2; if ((var2 = this.mActivity) != null) { var2.runOnUiThread(var1); } else if (Thread.currentThread() != Looper.getMainLooper().getThread()) { this.mHandler.post(var1); } else { var1.run(); } } void postOnUiThread(Runnable var1) { (new Handler(Looper.getMainLooper())).post(var1); } /** @deprecated */ public void init(int var1, boolean var2) { } /** @deprecated */ public View getView() { return this; } /** @deprecated */ public Bundle getSettings() { return Bundle.EMPTY; } /** @deprecated */ public void quit() { this.destroy(); } public void newIntent(Intent var1) { u.Log(4, "onNewIntent"); this.m_launchUri = var1.getData(); F var10000 = this.m_MainThread; E var2 = UnityPlayer.E.i; Handler var3; if ((var3 = var10000.a) != null) { Message.obtain(var3, 2269, var2).sendToTarget(); } } public void destroy() { u.Log(4, "onDestroy"); Camera2Wrapper var1; if ((var1 = this.m_Camera2Wrapper) != null) { var1.a(); this.m_Camera2Wrapper = null; } HFPStatus var5; if ((var5 = this.m_HFPStatus) != null) { var5.a(); this.m_HFPStatus = null; } NetworkConnectivity var6; if ((var6 = this.m_NetworkConnectivity) != null) { var6.a(); this.m_NetworkConnectivity = null; } this.mQuitting = true; if (!this.mState.b()) { this.pause(); } E var7 = UnityPlayer.E.c; Handler var2; if ((var2 = this.m_MainThread.a) != null) { Message.obtain(var2, 2269, var7).sendToTarget(); } label41: { label52: { F var10000; boolean var10001; int var8; try { var10000 = this.m_MainThread; var8 = UnityPlayer.SynchronizationTimeout.Destroy.getTimeout(); } catch (InterruptedException var4) { var10001 = false; break label52; } long var9 = (long)var8; try { var10000.join(var9); break label41; } catch (InterruptedException var3) { var10001 = false; } } this.m_MainThread.interrupt(); } if (J.d()) { this.removeAllViews(); } if (this.mProcessKillRequested) { this.m_UnityPlayerLifecycleEvents.onUnityPlayerQuitted(); this.kill(); } unloadNative(); } protected void kill() { Process.killProcess(Process.myPid()); } public void pause() { u.Log(4, "onPause"); GoogleARCoreApi var1; if ((var1 = this.m_ARCoreApi) != null) { var1.pauseARCore(); } U var2; if ((var2 = this.mVideoPlayerProxy) != null) { var2.c(); } AudioVolumeHandler var3; if ((var3 = this.m_AudioVolumeHandler) != null) { var3.a(); this.m_AudioVolumeHandler = null; } OrientationLockListener var4; if ((var4 = this.m_OrientationLockListener) != null) { var4.a(); this.m_OrientationLockListener = null; } this.pauseUnity(); } public void resume() { u.Log(4, "onResume"); GoogleARCoreApi var1; if ((var1 = this.m_ARCoreApi) != null) { var1.resumeARCore(); } this.mState.e(false); U var2; if ((var2 = this.mVideoPlayerProxy) != null) { var2.d(); } this.checkResumePlayer(); if (J.d()) { this.nativeRestartActivityIndicator(); } if (this.m_AudioVolumeHandler == null) { this.m_AudioVolumeHandler = new AudioVolumeHandler(this.mContext); } if (this.m_OrientationLockListener == null && J.d()) { this.m_OrientationLockListener = new OrientationLockListener(this.mContext); } this.prevConfig = this.getResources().getConfiguration(); } public void lowMemory() { if (J.d()) { Runnable var1; var1 = new Runnable() { public void run() { UnityPlayer.this.nativeLowMemory(); } }.(); this.queueGLThreadEvent(var1); } } public void unload() { this.nativeApplicationUnload(); } protected boolean skipPermissionsDialog() { Activity var1; return (var1 = this.mActivity) != null ? UnityPermissions.skipPermissionsDialog(var1) : false; } protected void requestUserAuthorization(String var1) { if (var1 != null && !var1.isEmpty() && this.mActivity != null) { UnityPermissions.ModalWaitForPermissionResponse var2; UnityPermissions.ModalWaitForPermissionResponse var10000 = var2 = new UnityPermissions.ModalWaitForPermissionResponse; var2.(); UnityPermissions.requestUserPermissions(this.mActivity, new String[]{var1}, var2); var10000.waitForResponse(); } } protected int getNetworkConnectivity() { NetworkConnectivity var1; if ((var1 = this.m_NetworkConnectivity) != null) { return var1.b(); } else { if (PlatformSupport.NOUGAT_SUPPORT) { this.m_NetworkConnectivity = new NetworkConnectivityNougat(this.mContext); } else { this.m_NetworkConnectivity = new NetworkConnectivity(this.mContext); } return this.m_NetworkConnectivity.b(); } } public void configurationChanged(Configuration var1) { int var2; if (((var2 = this.prevConfig.diff(var1)) & 256) != 0 || (var2 & 1024) != 0 || (var2 & 2048) != 0 || (var2 & 128) != 0) { this.nativeHidePreservedContent(); } UnityPlayer var10000 = this; UnityPlayer var10001 = this; Configuration var3; var3 = new Configuration.(var1); var10001.prevConfig = var3; u.Log(4, "onConfigurationChanged"); U var4; if ((var4 = var10000.mVideoPlayerProxy) != null) { var4.b(); } } public void windowFocusChanged(boolean var1) { u.Log(4, "windowFocusChanged: " + var1); this.mState.b(var1); com.unity3d.player.B var2; if (this.mState.a() && ((var2 = this.mSoftInputDialog) == null || var2.d)) { E var3; Handler var4; if (var1) { var3 = UnityPlayer.E.g; if ((var4 = this.m_MainThread.a) != null) { Message.obtain(var4, 2269, var3).sendToTarget(); } } else { var3 = UnityPlayer.E.f; if ((var4 = this.m_MainThread.a) != null) { Message.obtain(var4, 2269, var3).sendToTarget(); } } this.checkResumePlayer(); } } protected boolean loadLibrary(String var1) { try { System.loadLibrary(var1); return true; } catch (UnsatisfiedLinkError var2) { return false; } catch (Exception var3) { return false; } } protected void addPhoneCallListener() { this.m_AddPhoneCallListener = true; this.m_TelephonyManager.listen(this.m_PhoneCallListener, 32); } protected void showSoftInput(String var1, int var2, boolean var3, boolean var4, boolean var5, boolean var6, String var7, int var8, boolean var9, boolean var10) { b var11; var11 = new b.(this, this, var1, var2, var3, var4, var5, var6, var7, var8, var9, var10); this.postOnUiThread(var11); } protected void hideSoftInput() { d var1; var1 = new d.(this); this.postOnUiThread(var1); } protected void setSoftInputStr(String var1) { e var2; var2 = new e.(this, var1); this.runOnUiThread(var2); } protected void setCharacterLimit(int var1) { f var2; var2 = new f.(this, var1); this.runOnUiThread(var2); } protected void setHideInputField(boolean var1) { g var2; var2 = new g.(this, var1); this.runOnUiThread(var2); } protected void setSelection(int var1, int var2) { Runnable var3; var3 = new Runnable() { { this.a = var2; this.b = var3; } public void run() { com.unity3d.player.B var1; if ((var1 = UnityPlayer.this.mSoftInputDialog) != null) { var10001 = this; int var2 = this.a; var1.a(var2, var10001.b); } } }.(var1, var2); this.runOnUiThread(var3); } protected String getKeyboardLayout() { com.unity3d.player.B var1; return (var1 = this.mSoftInputDialog) == null ? null : var1.a(); } protected void reportSoftInputStr(String var1, int var2, boolean var3) { if (var2 == 1) { this.hideSoftInput(); } G var4; var4 = new G() { { this.b = var2; this.c = var3; this.d = var4; } public void a() { if (this.b) { UnityPlayer.this.nativeSoftInputCanceled(); } else { String var1; if ((var1 = this.c) != null) { UnityPlayer.this.nativeSetInputString(var1); } } if (this.d == 1) { UnityPlayer.this.nativeSoftInputClosed(); } } }.(var3, var1, var2); this.queueGLThreadEvent(var4); } protected void reportSoftInputSelection(int var1, int var2) { G var3; var3 = new G() { { this.b = var2; this.c = var3; } public void a() { UnityPlayer var10000 = UnityPlayer.this; var10001 = this; int var1 = this.b; var10000.nativeSetInputSelection(var1, var10001.c); } }.(var1, var2); this.queueGLThreadEvent(var3); } protected void reportSoftInputArea(Rect var1) { G var2; var2 = new G() { { this.b = var2; } public void a() { UnityPlayer var10000 = UnityPlayer.this; Rect var10001 = this.b; int var4 = var10001.left; int var1 = var10001.top; int var2 = var10001.right; int var3 = var10001.bottom; var10000.nativeSetInputArea(var4, var1, var2, var3); } }.(var1); this.queueGLThreadEvent(var2); } protected void reportSoftInputIsVisible(boolean var1) { G var2; var2 = new G() { { this.b = var2; } public void a() { UnityPlayer.this.nativeSetKeyboardIsVisible(this.b); } }.(var1); this.queueGLThreadEvent(var2); } protected void setClipboardText(String var1) { UnityPlayer var10000 = this; ClipData var2 = ClipData.newPlainText("Text", var1); var10000.m_ClipboardManager.setPrimaryClip(var2); } protected String getClipboardText() { String var1 = ""; ClipData var2; if ((var2 = this.m_ClipboardManager.getPrimaryClip()) != null) { var1 = var2.getItemAt(0).coerceToText(this.mContext).toString(); } return var1; } protected String getLaunchURL() { Uri var1; return (var1 = this.m_launchUri) != null ? var1.toString() : null; } protected boolean initializeGoogleAr() { if (this.m_ARCoreApi == null && this.mActivity != null && this.getARCoreEnabled()) { GoogleARCoreApi var1; GoogleARCoreApi var10001 = var1 = new GoogleARCoreApi; var1.(); this.m_ARCoreApi = var1; var10001.initializeARCore(this.mActivity); if (!this.mState.b()) { this.m_ARCoreApi.resumeARCore(); } } return false; } protected boolean showVideoPlayer(String var1, int var2, int var3, int var4, boolean var5, int var6, int var7) { if (this.mVideoPlayerProxy == null) { U var8; var8 = new U.(this); this.mVideoPlayerProxy = var8; } U var10000 = this.mVideoPlayerProxy; int var10002 = var6; Context var14 = this.mContext; long var16 = (long)var10002; long var10 = (long)var7; Context var10001 = var14; U.a var15; var15 = new U.a() { public void a() { UnityPlayer.this.mVideoPlayerProxy = null; } }.(); boolean var12; if (var12 = var10000.a(var10001, var1, var2, var3, var4, var5, var16, var10, var15)) { Runnable var13; var13 = new Runnable() { public void run() { Activity var1; UnityPlayer var2; if (UnityPlayer.this.nativeIsAutorotationOn() && (var1 = (var2 = UnityPlayer.this).mActivity) != null) { var1.setRequestedOrientation(var2.mInitialScreenOrientation); } } }.(); this.runOnUiThread(var13); } return var12; } protected void pauseJavaAndCallUnloadCallback() { Runnable var1; var1 = new Runnable() { public void run() { UnityPlayer.this.pause(); UnityPlayer.this.windowFocusChanged(false); UnityPlayer.this.m_UnityPlayerLifecycleEvents.onUnityPlayerUnloaded(); } }.(); this.runOnUiThread(var1); } protected boolean isUaaLUseCase() { Activity var1; if ((var1 = this.mActivity) == null) { return false; } else { String var2; return (var2 = var1.getCallingPackage()) != null && var2.equals(this.mContext.getPackageName()); } } protected int getUaaLLaunchProcessType() { String var1; return (var1 = this.getProcessName()) != null && !var1.equals(this.mContext.getPackageName()) ? 1 : 0; } protected int getSplashMode() { try { return this.getApplicationInfo().metaData.getInt("unity.splash-mode"); } catch (Exception var1) { return 0; } } protected void executeGLThreadJobs() { Runnable var1; while((var1 = (Runnable)this.m_Events.poll()) != null) { var1.run(); } } protected void disableLogger() { u.a = true; } void queueGLThreadEvent(Runnable var1) { if (J.d()) { if (Thread.currentThread() == this.m_MainThread) { var1.run(); } else { this.m_Events.add(var1); } } } protected boolean isFinishing() { if (this.mQuitting) { return true; } else { Activity var1; if ((var1 = this.mActivity) != null) { this.mQuitting = var1.isFinishing(); } return this.mQuitting; } } public boolean injectEvent(InputEvent var1) { return !J.d() ? false : this.nativeInjectEvent(var1); } public boolean onKeyUp(int var1, KeyEvent var2) { return this.injectEvent(var2); } public boolean onKeyDown(int var1, KeyEvent var2) { return this.injectEvent(var2); } public boolean onKeyMultiple(int var1, int var2, KeyEvent var3) { return this.injectEvent(var3); } public boolean onKeyLongPress(int var1, KeyEvent var2) { return this.injectEvent(var2); } public boolean onTouchEvent(MotionEvent var1) { return !this.mGlView.c() ? this.injectEvent(var1) : false; } public boolean onGenericMotionEvent(MotionEvent var1) { return !this.mGlView.c() ? this.injectEvent(var1) : false; } public boolean addViewToPlayer(View var1, boolean var2) { I var3; if (var2) { var3 = this.mGlView; } else { var3 = null; } this.swapViews(var1, var3); boolean var5; if (var1.getParent() == this) { var5 = true; } else { var5 = false; } if (var2 && this.mGlView.getParent() == null) { var2 = true; } else { var2 = false; } boolean var4; if (this.mGlView.getParent() == this) { var4 = true; } else { var4 = false; } boolean var6; if (!var5 || !var2 && !var4) { var6 = false; } else { var6 = true; } if (!var6) { if (!var5) { u.Log(6, "addViewToPlayer: Failure adding view to hierarchy"); } if (!var2 && !var4) { u.Log(6, "addViewToPlayer: Failure removing old view from hierarchy"); } } return var6; } public void removeViewFromPlayer(View var1) { this.swapViews(this.mGlView, var1); boolean var4; if (var1.getParent() == null) { var4 = true; } else { var4 = false; } boolean var3; if (this.mGlView.getParent() == this) { var3 = true; } else { var3 = false; } if (!var4 || !var3) { if (!var4) { u.Log(6, "removeViewFromPlayer: Failure removing view from hierarchy"); } if (!var3) { u.Log(6, "removeVireFromPlayer: Failure agging old view to hierarchy"); } } } public void reportError(String var1, String var2) { StringBuilder var10000 = new StringBuilder(); var10000.append(var1); var10000.append(": "); var10000.append(var2); u.Log(6, var10000.toString()); } public String getNetworkProxySettings(String var1) { String var2; String var4; if (var1.startsWith("http:")) { var4 = "http.proxyHost"; var1 = "http.proxyPort"; var2 = "http.nonProxyHosts"; } else { if (!var1.startsWith("https:")) { return null; } var4 = "https.proxyHost"; var1 = "https.proxyPort"; var2 = "http.nonProxyHosts"; } if ((var4 = System.getProperties().getProperty(var4)) != null && !"".equals(var4)) { StringBuilder var3; var3 = new StringBuilder.(var4); if ((var4 = System.getProperties().getProperty(var1)) != null && !"".equals(var4)) { var3.append(":").append(var4); } if ((var4 = System.getProperties().getProperty(var2)) != null && !"".equals(var4)) { var3.append('\n').append(var4); } return var3.toString(); } else { return null; } } public boolean startOrientationListener(int var1) { if (this.mOrientationListener != null) { u.Log(5, "Orientation Listener already started."); return false; } else { OrientationEventListener var2; OrientationEventListener var10001 = var2 = new OrientationEventListener() { public void onOrientationChanged(int var1) { UnityPlayer var2; F var10000 = (var2 = UnityPlayer.this).m_MainThread; var10000.f = var2.mNaturalOrientation; var10000.g = var1; E var3 = UnityPlayer.E.j; Handler var4; if ((var4 = var10000.a) != null) { Message.obtain(var4, 2269, var3).sendToTarget(); } } }; var10001.(this.mContext, var1); this.mOrientationListener = var10001; if (var2.canDetectOrientation()) { this.mOrientationListener.enable(); return true; } else { u.Log(5, "Orientation Listener cannot detect orientation."); return false; } } } public boolean stopOrientationListener() { OrientationEventListener var1; if ((var1 = this.mOrientationListener) == null) { u.Log(5, "Orientation Listener was not started."); return false; } else { var1.disable(); this.mOrientationListener = null; return true; } } public void setMainSurfaceViewAspectRatio(float var1) { if (this.mGlView != null) { Runnable var2; var2 = new Runnable() { { this.a = var2; } public void run() { UnityPlayer.this.mGlView.a(this.a); } }.(var1); this.runOnUiThread(var2); } } public void setScreenBrightness(float var1) { var1 = Math.max(0.04F, var1); if (this.m_Window != null && this.getScreenBrightness() != var1) { Runnable var2; var2 = new Runnable() { { this.a = var2; } public void run() { WindowManager.LayoutParams var1; (var1 = UnityPlayer.this.m_Window.getAttributes()).screenBrightness = this.a; UnityPlayer.this.m_Window.setAttributes(var1); } }.(var1); this.runOnUiThread(var2); } } public float getScreenBrightness() { Window var1; if ((var1 = this.m_Window) == null) { return 1.0F; } else { float var4; if ((var4 = var1.getAttributes().screenBrightness) < 0.0F) { int var3 = android.provider.Settings.System.getInt(this.getContext().getContentResolver(), "screen_brightness", 255); if (PlatformSupport.PIE_SUPPORT) { double var5 = (Math.log((double)var3) * 19.811 - 9.411) / 100.0; var5 = Math.min(1.0, var5); return (float)Math.max(0.0, var5); } else { return (float)var3 / 255.0F; } } else { return var4; } } } private abstract class G implements Runnable { private G() { } public final void run() { if (!UnityPlayer.this.isFinishing()) { this.a(); } } public abstract void a(); } class B implements SensorEventListener { B(UnityPlayer var1) { } public void onAccuracyChanged(Sensor var1, int var2) { } public void onSensorChanged(SensorEvent var1) { } } private class D extends PhoneStateListener { private D() { } public void onCallStateChanged(int var1, String var2) { UnityPlayer var3 = UnityPlayer.this; boolean var4; if (var1 == 1) { var4 = true; } else { var4 = false; } var3.nativeMuteMasterAudio(var4); } } private class F extends Thread { Handler a; boolean b = false; boolean c = false; C d; int e; int f; int g; int h; private F() { this.d = UnityPlayer.C.b; this.e = 0; this.h = 5; } public void run() { this.setName("UnityMain"); Looper.prepare(); Handler var1; Handler var10001 = var1 = new Handler; Looper var2 = Looper.myLooper(); Handler.Callback var3; var3 = new Handler.Callback() { { this.a = var1; } private void a() { UnityPlayer.F var1; if ((var1 = this.a).d == UnityPlayer.C.c && var1.c) { var1.i.nativeFocusChanged(true); this.a.d = UnityPlayer.C.a; } } public boolean handleMessage(Message param1) { // $FF: Couldn't be decompiled } }.(); var10001.(var2, var3); this.a = var1; Looper.loop(); } } static enum C { a, b, c; private C() { } } static enum E { a, b, c, d, e, f, g, h, i, j; private E() { } } public static enum SynchronizationTimeout { Pause, SurfaceDetach, Destroy; final int value; private int m_TimeoutMilliseconds; private SynchronizationTimeout(int var3) { this.value = var3; this.m_TimeoutMilliseconds = 2000; } public static void setTimeoutForAll(int var0) { SynchronizationTimeout[] var1; int var2 = (var1 = (SynchronizationTimeout[])SynchronizationTimeout.class.getEnumConstants()).length; for(int var3 = 0; var3 < var2; ++var3) { var1[var3].setTimeout(var0); } } static { SynchronizationTimeout var0; SynchronizationTimeout var10000 = var0 = new SynchronizationTimeout; var10000.(0); Pause = var10000; SynchronizationTimeout var1; var10000 = var1 = new SynchronizationTimeout; var10000.(1); SurfaceDetach = var10000; SynchronizationTimeout var2; var10000 = var2 = new SynchronizationTimeout; var10000.(2); Destroy = var10000; } public void setTimeout(int var1) { this.m_TimeoutMilliseconds = var1; } public int getTimeout() { return this.m_TimeoutMilliseconds; } } } ```
timbotimbo commented 7 months ago

I feel like replicating the internals is just looking for trouble. I wouldn't be surprised if it breaks again without even a changelog mention in the next Unity release.

A third option would be to make people use >= 2022.3.19 as it seems to be fixed there. But given that there is no bugfix documented anywhere, this might just be an accidental fix that breaks again in the future.

jamesncl commented 7 months ago

On balance I think it's best to go with your third option. I've just published version 1.0.4 with these details in the README. Thanks for your help!