From 85a0b935c9d70a7f082eb3df5f3c9f61ea48009a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Nov 2023 19:01:08 +0100 Subject: [PATCH] Always assign a system context as base context FakeContext used ActivityThread.getSystemContext() as base context only in some cases, because it caused problems on some devices: - warnings on Xiaomi devices [1], which are now fixed by b8c5853aa6ac9cfbe3fb4e46bf10978b3fa212e3 - issues related to Looper [2], which are solved by just calling Looper.prepare*() Therefore, we can now always assign a base context, which simplifies and helps to solve camera issues on some devices (#4392). [1] [2] Fixes #4392 --- .../com/genymobile/scrcpy/FakeContext.java | 6 +- .../com/genymobile/scrcpy/Workarounds.java | 71 ++++++++----------- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 6501d4cf..520e0378 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -2,11 +2,11 @@ package com.genymobile.scrcpy; import android.annotation.TargetApi; import android.content.AttributionSource; -import android.content.MutableContextWrapper; +import android.content.ContextWrapper; import android.os.Build; import android.os.Process; -public final class FakeContext extends MutableContextWrapper { +public final class FakeContext extends ContextWrapper { public static final String PACKAGE_NAME = "com.android.shell"; public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29 @@ -18,7 +18,7 @@ public final class FakeContext extends MutableContextWrapper { } private FakeContext() { - super(null); + super(Workarounds.getSystemContext()); } @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index e8da9540..5b3a5c8c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -21,18 +21,34 @@ import java.lang.reflect.Method; public final class Workarounds { - private static Class activityThreadClass; - private static Object activityThread; + private static final Class ACTIVITY_THREAD_CLASS; + private static final Object ACTIVITY_THREAD; + + static { + prepareMainLooper(); + + try { + // ActivityThread activityThread = new ActivityThread(); + ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread"); + Constructor activityThreadConstructor = ACTIVITY_THREAD_CLASS.getDeclaredConstructor(); + activityThreadConstructor.setAccessible(true); + ACTIVITY_THREAD = activityThreadConstructor.newInstance(); + + // ActivityThread.sCurrentActivityThread = activityThread; + Field sCurrentActivityThreadField = ACTIVITY_THREAD_CLASS.getDeclaredField("sCurrentActivityThread"); + sCurrentActivityThreadField.setAccessible(true); + sCurrentActivityThreadField.set(null, ACTIVITY_THREAD); + } catch (Exception e) { + throw new AssertionError(e); + } + } private Workarounds() { // not instantiable } public static void apply(boolean audio, boolean camera) { - Workarounds.prepareMainLooper(); - boolean mustFillAppInfo = false; - boolean mustFillBaseContext = false; boolean mustFillAppContext = false; if (Build.BRAND.equalsIgnoreCase("meizu")) { @@ -53,7 +69,6 @@ public final class Workarounds { // - // - mustFillAppInfo = true; - mustFillBaseContext = true; mustFillAppContext = true; } @@ -66,15 +81,11 @@ public final class Workarounds { if (camera) { mustFillAppInfo = true; - mustFillBaseContext = true; } if (mustFillAppInfo) { Workarounds.fillAppInfo(); } - if (mustFillBaseContext) { - Workarounds.fillBaseContext(); - } if (mustFillAppContext) { Workarounds.fillAppContext(); } @@ -93,27 +104,9 @@ public final class Workarounds { Looper.prepareMainLooper(); } - @SuppressLint("PrivateApi,DiscouragedPrivateApi") - private static void fillActivityThread() throws Exception { - if (activityThread == null) { - // ActivityThread activityThread = new ActivityThread(); - activityThreadClass = Class.forName("android.app.ActivityThread"); - Constructor activityThreadConstructor = activityThreadClass.getDeclaredConstructor(); - activityThreadConstructor.setAccessible(true); - activityThread = activityThreadConstructor.newInstance(); - - // ActivityThread.sCurrentActivityThread = activityThread; - Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); - sCurrentActivityThreadField.setAccessible(true); - sCurrentActivityThreadField.set(null, activityThread); - } - } - @SuppressLint("PrivateApi,DiscouragedPrivateApi") private static void fillAppInfo() { try { - fillActivityThread(); - // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); Class appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); Constructor appBindDataConstructor = appBindDataClass.getDeclaredConstructor(); @@ -129,9 +122,9 @@ public final class Workarounds { appInfoField.set(appBindData, applicationInfo); // activityThread.mBoundApplication = appBindData; - Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication"); + Field mBoundApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mBoundApplication"); mBoundApplicationField.setAccessible(true); - mBoundApplicationField.set(activityThread, appBindData); + mBoundApplicationField.set(ACTIVITY_THREAD, appBindData); } catch (Throwable throwable) { // this is a workaround, so failing is not an error Ln.d("Could not fill app info: " + throwable.getMessage()); @@ -141,33 +134,29 @@ public final class Workarounds { @SuppressLint("PrivateApi,DiscouragedPrivateApi") private static void fillAppContext() { try { - fillActivityThread(); - Application app = new Application(); Field baseField = ContextWrapper.class.getDeclaredField("mBase"); baseField.setAccessible(true); baseField.set(app, FakeContext.get()); // activityThread.mInitialApplication = app; - Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication"); + Field mInitialApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mInitialApplication"); mInitialApplicationField.setAccessible(true); - mInitialApplicationField.set(activityThread, app); + mInitialApplicationField.set(ACTIVITY_THREAD, app); } catch (Throwable throwable) { // this is a workaround, so failing is not an error Ln.d("Could not fill app context: " + throwable.getMessage()); } } - private static void fillBaseContext() { + static Context getSystemContext() { try { - fillActivityThread(); - - Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext"); - Context context = (Context) getSystemContextMethod.invoke(activityThread); - FakeContext.get().setBaseContext(context); + Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext"); + return (Context) getSystemContextMethod.invoke(ACTIVITY_THREAD); } catch (Throwable throwable) { // this is a workaround, so failing is not an error - Ln.d("Could not fill base context: " + throwable.getMessage()); + Ln.d("Could not get system context: " + throwable.getMessage()); + return null; } }