Compare commits

..

3 Commits

Author SHA1 Message Date
Romain Vimont
9bff8ccadb Apply workaround on "honor" devices
This makes audio work on those devices.

Fixes #4015 <https://github.com/Genymobile/scrcpy/issues/4015>
2023-06-17 00:25:10 +02:00
Romain Vimont
0d4157357a Use system context as base context
DONOTMERGE: it causes #994 on Xiaomi devices

This allows to make Context.getPackageManager() work.

Fixes #4015 <https://github.com/Genymobile/scrcpy/issues/4015>
Refs <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1594262721>

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
2023-06-17 00:25:10 +02:00
Romain Vimont
95e61e2a0b Move ActivityThread instance
An ActivityThread instance will be needed from several classes.
2023-06-17 00:25:10 +02:00
4 changed files with 76 additions and 51 deletions

View File

@@ -1,24 +1,42 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ActivityThread;
import android.annotation.TargetApi;
import android.content.AttributionSource;
import android.content.MutableContextWrapper;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Build;
import android.os.Process;
public final class FakeContext extends MutableContextWrapper {
import java.lang.reflect.Method;
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
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(null);
super(retrieveSystemContext());
}
@Override

View File

@@ -101,23 +101,15 @@ public final class Server {
Workarounds.prepareMainLooper();
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>
Workarounds.fillAppInfo();
} else if (Build.BRAND.equalsIgnoreCase("honor")) {
// Honor devices require both a system context (as a base context of FakeContext) and the same workarounds as for Meizu phones:
// - <https://github.com/Genymobile/scrcpy/issues/4015>
// The system context must not be set for all devices, it would cause other problems:
// - <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1595382142>
// - <https://github.com/Genymobile/scrcpy/issues/3805#issuecomment-1596148031>
Workarounds.fillBaseContext();
// 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();
}

View File

@@ -1,10 +1,11 @@
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;
@@ -21,9 +22,7 @@ import java.lang.reflect.Method;
public final class Workarounds {
private static Class<?> activityThreadClass;
private static Object activityThread;
private static boolean currentActivityThreadInitialized;
private static boolean activityThreadFilled;
private Workarounds() {
// not instantiable
@@ -42,27 +41,18 @@ public final class Workarounds {
Looper.prepareMainLooper();
}
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
private static void initActivityThread() 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();
}
}
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
private static void fillActivityThread() throws Exception {
initActivityThread();
if (!activityThreadFilled) {
Class<?> activityThreadClass = ActivityThread.getActivityThreadClass();
Object activityThread = ActivityThread.getActivityThread();
if (!currentActivityThreadInitialized) {
// ActivityThread.sCurrentActivityThread = activityThread;
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
sCurrentActivityThreadField.set(null, activityThread);
currentActivityThreadInitialized = true;
activityThreadFilled = true;
}
}
@@ -85,6 +75,9 @@ 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);
@@ -105,6 +98,9 @@ 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);
@@ -115,19 +111,6 @@ public final class Workarounds {
}
}
public static void fillBaseContext() {
try {
initActivityThread();
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) {

View File

@@ -0,0 +1,32 @@
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;
}
}