package com.genymobile.scrcpy;

import android.annotation.SuppressLint;
import android.content.pm.ApplicationInfo;
import android.os.Looper;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public final class Workarounds {
    private Workarounds() {
        // not instantiable
    }

    public static void prepareMainLooper() {
        // Some devices internally create a Handler when creating an input Surface, causing an exception:
        //   "Can't create handler inside thread that has not called Looper.prepare()"
        // <https://github.com/Genymobile/scrcpy/issues/240>
        //
        // Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException:
        //   "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue'
        //    on a null object reference"
        // <https://github.com/Genymobile/scrcpy/issues/921>
        Looper.prepareMainLooper();
    }

    @SuppressLint("PrivateApi")
    public static void fillAppInfo() {
        try {
            // ActivityThread activityThread = new ActivityThread();
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
            activityThreadConstructor.setAccessible(true);
            Object activityThread = activityThreadConstructor.newInstance();

            // ActivityThread.sCurrentActivityThread = activityThread;
            Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            sCurrentActivityThreadField.set(null, activityThread);

            // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
            Class<?> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
            Constructor<?> appBindDataConstructor = appBindDataClass.getDeclaredConstructor();
            appBindDataConstructor.setAccessible(true);
            Object appBindData = appBindDataConstructor.newInstance();

            ApplicationInfo applicationInfo = new ApplicationInfo();
            applicationInfo.packageName = "com.genymobile.scrcpy";

            // appBindData.appInfo = applicationInfo;
            Field appInfoField = appBindDataClass.getDeclaredField("appInfo");
            appInfoField.setAccessible(true);
            appInfoField.set(appBindData, applicationInfo);

            // activityThread.mBoundApplication = appBindData;
            Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
            mBoundApplicationField.setAccessible(true);
            mBoundApplicationField.set(activityThread, appBindData);
        } catch (Throwable throwable) {
            // this is a workaround, so failing is not an error
            Ln.w("Could not fill app info: " + throwable.getMessage());
        }
    }
}

@@ -65,16 +65,116 @@
 ssLint;%0A
+import android.app.Application;%0Aimport android.app.Instrumentation;%0Aimport android.content.Context;%0A
 import a
@@ -304,16 +304,49 @@
 t.Field;
+%0Aimport java.lang.reflect.Method;
 %0A%0Apublic
@@ -2954,16 +2954,639 @@
 ndData);
+%0A%0A            // Context ctx = activityThread.getSystemContext();%0A            Method getSystemContextMethod = activityThreadClass.getDeclaredMethod(%22getSystemContext%22);%0A            Context ctx = (Context) getSystemContextMethod.invoke(activityThread);%0A%0A            Application app = Instrumentation.newApplication(Application.class, ctx);%0A%0A            // activityThread.mInitialApplication = app;%0A            Field mInitialApplicationField = activityThreadClass.getDeclaredField(%22mInitialApplication%22);%0A            mInitialApplicationField.setAccessible(true);%0A            mInitialApplicationField.set(activityThread, app);
 %0A       

Initialize Application object to avoid NPE