diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 5800487d..244913cf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -81,15 +81,15 @@ public final class Server { // But only apply when strictly necessary, since workarounds can cause other issues: // - // - - boolean mustFillAppInfo = Build.BRAND.equalsIgnoreCase("meizu"); + if (Build.BRAND.equalsIgnoreCase("meizu")) { + Workarounds.fillAppInfo(); + } // Before Android 11, audio is not supported. // Since Android 12, we can properly set a context on the AudioRecord. - // Only on Android 11 we must fill app info for the AudioRecord to work. - mustFillAppInfo |= audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R; - - if (mustFillAppInfo) { - Workarounds.fillAppInfo(); + // Only on Android 11 we must fill the application context for the AudioRecord to work. + if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + Workarounds.fillAppContext(); } List asyncProcessors = new ArrayList<>(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 64cc1272..89380ece 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -10,6 +10,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; public final class Workarounds { + + private static Class activityThreadClass; + private static Object activityThread; + private Workarounds() { // not instantiable } @@ -28,18 +32,25 @@ public final class Workarounds { } @SuppressLint("PrivateApi,DiscouragedPrivateApi") - public static void fillAppInfo() { - try { + private static void fillActivityThread() throws Exception { + if (activityThread == null) { // ActivityThread activityThread = new ActivityThread(); - Class activityThreadClass = Class.forName("android.app.ActivityThread"); + activityThreadClass = Class.forName("android.app.ActivityThread"); Constructor activityThreadConstructor = activityThreadClass.getDeclaredConstructor(); activityThreadConstructor.setAccessible(true); - Object activityThread = activityThreadConstructor.newInstance(); + activityThread = activityThreadConstructor.newInstance(); // ActivityThread.sCurrentActivityThread = activityThread; Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); sCurrentActivityThreadField.setAccessible(true); sCurrentActivityThreadField.set(null, activityThread); + } + } + + @SuppressLint("PrivateApi,DiscouragedPrivateApi") + public static void fillAppInfo() { + try { + fillActivityThread(); // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); Class appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); @@ -59,6 +70,16 @@ public final class Workarounds { 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.d("Could not fill app info: " + throwable.getMessage()); + } + } + + @SuppressLint("PrivateApi,DiscouragedPrivateApi") + public static void fillAppContext() { + try { + fillActivityThread(); Application app = Application.class.newInstance(); Field baseField = ContextWrapper.class.getDeclaredField("mBase"); @@ -71,7 +92,7 @@ public final class Workarounds { mInitialApplicationField.set(activityThread, app); } catch (Throwable throwable) { // this is a workaround, so failing is not an error - Ln.d("Could not fill app info: " + throwable.getMessage()); + Ln.d("Could not fill app context: " + throwable.getMessage()); } } }