Compare commits
2 Commits
activity_t
...
honor.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c2d4b18b2 | ||
|
|
8bf276486f |
@@ -1,42 +1,24 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ActivityThread;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.MutableContextWrapper;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class FakeContext extends ContextWrapper {
|
||||
public final class FakeContext extends MutableContextWrapper {
|
||||
|
||||
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
|
||||
|
||||
private static final FakeContext INSTANCE = new FakeContext();
|
||||
|
||||
private static Context retrieveSystemContext() {
|
||||
try {
|
||||
Class<?> activityThreadClass = ActivityThread.getActivityThreadClass();
|
||||
Object activityThread = ActivityThread.getActivityThread();
|
||||
|
||||
Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext");
|
||||
return (Context) getSystemContextMethod.invoke(activityThread);
|
||||
} catch (Exception e) {
|
||||
Ln.e("Cannot retrieve system context", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static FakeContext get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private FakeContext() {
|
||||
super(retrieveSystemContext());
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -99,26 +99,7 @@ public final class Server {
|
||||
boolean audio = options.getAudio();
|
||||
boolean sendDummyByte = options.getSendDummyByte();
|
||||
|
||||
Workarounds.prepareMainLooper();
|
||||
|
||||
// Workarounds must be applied for Meizu phones:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/240>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/365>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/2656>
|
||||
//
|
||||
// But only apply when strictly necessary, since workarounds can cause other issues:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
||||
if (Build.BRAND.equalsIgnoreCase("meizu") || Build.BRAND.equalsIgnoreCase("honor")) {
|
||||
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 the application context for the AudioRecord to work.
|
||||
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
Workarounds.fillAppContext();
|
||||
}
|
||||
Workarounds.apply(audio);
|
||||
|
||||
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ActivityThread;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.media.AudioAttributes;
|
||||
@@ -22,14 +21,61 @@ import java.lang.reflect.Method;
|
||||
|
||||
public final class Workarounds {
|
||||
|
||||
private static boolean activityThreadFilled;
|
||||
private static Class<?> activityThreadClass;
|
||||
private static Object activityThread;
|
||||
|
||||
private Workarounds() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
public static void apply(boolean audio) {
|
||||
Workarounds.prepareMainLooper();
|
||||
|
||||
boolean mustFillAppInfo = false;
|
||||
boolean mustFillBaseContext = false;
|
||||
boolean mustFillAppContext = false;
|
||||
|
||||
|
||||
if (Build.BRAND.equalsIgnoreCase("meizu")) {
|
||||
// Workarounds must be applied for Meizu phones:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/240>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/365>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/2656>
|
||||
//
|
||||
// But only apply when strictly necessary, since workarounds can cause other issues:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
||||
mustFillAppInfo = true;
|
||||
} else if (Build.BRAND.equalsIgnoreCase("honor")) {
|
||||
// Honor devices require in addition a system context as a base context of FakeContext:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/4015>
|
||||
// The system context must not be set for all devices, because it would cause other problems:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1595382142>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/3805#issuecomment-1596148031>
|
||||
mustFillBaseContext = true;
|
||||
mustFillAppContext = true;
|
||||
}
|
||||
|
||||
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
// 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 the application context for the AudioRecord to work.
|
||||
mustFillAppContext = true;
|
||||
}
|
||||
|
||||
if (mustFillAppInfo) {
|
||||
Workarounds.fillAppInfo();
|
||||
}
|
||||
if (mustFillBaseContext) {
|
||||
Workarounds.fillBaseContext();
|
||||
}
|
||||
if (mustFillAppContext) {
|
||||
Workarounds.fillAppContext();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void prepareMainLooper() {
|
||||
private 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>
|
||||
@@ -43,21 +89,22 @@ public final class Workarounds {
|
||||
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
private static void fillActivityThread() throws Exception {
|
||||
if (!activityThreadFilled) {
|
||||
Class<?> activityThreadClass = ActivityThread.getActivityThreadClass();
|
||||
Object activityThread = ActivityThread.getActivityThread();
|
||||
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);
|
||||
|
||||
activityThreadFilled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
public static void fillAppInfo() {
|
||||
private static void fillAppInfo() {
|
||||
try {
|
||||
fillActivityThread();
|
||||
|
||||
@@ -75,9 +122,6 @@ public final class Workarounds {
|
||||
appInfoField.setAccessible(true);
|
||||
appInfoField.set(appBindData, applicationInfo);
|
||||
|
||||
Class<?> activityThreadClass = ActivityThread.getActivityThreadClass();
|
||||
Object activityThread = ActivityThread.getActivityThread();
|
||||
|
||||
// activityThread.mBoundApplication = appBindData;
|
||||
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||
mBoundApplicationField.setAccessible(true);
|
||||
@@ -89,7 +133,7 @@ public final class Workarounds {
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
public static void fillAppContext() {
|
||||
private static void fillAppContext() {
|
||||
try {
|
||||
fillActivityThread();
|
||||
|
||||
@@ -98,9 +142,6 @@ public final class Workarounds {
|
||||
baseField.setAccessible(true);
|
||||
baseField.set(app, FakeContext.get());
|
||||
|
||||
Class<?> activityThreadClass = ActivityThread.getActivityThreadClass();
|
||||
Object activityThread = ActivityThread.getActivityThread();
|
||||
|
||||
// activityThread.mInitialApplication = app;
|
||||
Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
|
||||
mInitialApplicationField.setAccessible(true);
|
||||
@@ -111,6 +152,19 @@ public final class Workarounds {
|
||||
}
|
||||
}
|
||||
|
||||
public static void fillBaseContext() {
|
||||
try {
|
||||
fillActivityThread();
|
||||
|
||||
Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext");
|
||||
Context context = (Context) getSystemContextMethod.invoke(activityThread);
|
||||
FakeContext.get().setBaseContext(context);
|
||||
} catch (Throwable throwable) {
|
||||
// this is a workaround, so failing is not an error
|
||||
Ln.d("Could not fill base context: " + throwable.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.R)
|
||||
@SuppressLint("WrongConstant,MissingPermission,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi")
|
||||
public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
public class ActivityThread {
|
||||
|
||||
private static final Class<?> activityThreadClass;
|
||||
private static final Object activityThread;
|
||||
|
||||
static {
|
||||
try {
|
||||
activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
|
||||
activityThreadConstructor.setAccessible(true);
|
||||
activityThread = activityThreadConstructor.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private ActivityThread() {
|
||||
// only static methods
|
||||
}
|
||||
|
||||
public static Object getActivityThread() {
|
||||
return activityThread;
|
||||
}
|
||||
|
||||
public static Class<?> getActivityThreadClass() {
|
||||
return activityThreadClass;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user