Compare commits

..

5 Commits

Author SHA1 Message Date
Romain Vimont
ccaa832f48 Simplify --list-cameras output
Remove --video-source=camera from the output of --list-cameras (this is
implicit).
2023-11-05 21:44:33 +01:00
Romain Vimont
4e4ddc499f Return the FakeContext as application context
This avoids getApplicationContext() to return null and cause
NullPointerException.

Fixes #4392 <https://github.com/Genymobile/scrcpy/issues/4392#issuecomment-1792806080>
2023-11-03 19:07:15 +01:00
Romain Vimont
8d76b3e06d Fill application context for camera
Using the camera fails on some devices without a proper application
context.

Fixes #4392 <https://github.com/Genymobile/scrcpy/issues/4392>
2023-11-03 19:07:08 +01:00
Romain Vimont
85a0b935c9 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
   b8c5853aa6
 - 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] <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1595382142>
[2] <https://github.com/Genymobile/scrcpy/issues/3805#issuecomment-1596148031>

Fixes #4392 <https://github.com/Genymobile/scrcpy/issues/4392>
2023-11-03 19:05:50 +01:00
Romain Vimont
8c3e2bae7b Simplify Application instantiation
The constructor is public.
2023-11-03 19:05:28 +01:00
3 changed files with 42 additions and 76 deletions

View File

@@ -2,11 +2,12 @@ package com.genymobile.scrcpy;
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 {
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 +19,7 @@ public final class FakeContext extends MutableContextWrapper {
}
private FakeContext() {
super(Workarounds.retrieveSystemContext());
super(Workarounds.getSystemContext());
}
@Override
@@ -44,4 +45,9 @@ public final class FakeContext extends MutableContextWrapper {
public int getDeviceId() {
return 0;
}
@Override
public Context getApplicationContext() {
return this;
}
}

View File

@@ -93,7 +93,7 @@ public final class LogUtils {
builder.append("\n (none)");
} else {
for (String id : cameraIds) {
builder.append("\n --video-source=camera --camera-id=").append(id);
builder.append("\n --camera-id=").append(id);
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
int facing = characteristics.get(CameraCharacteristics.LENS_FACING);

View File

@@ -7,7 +7,6 @@ import android.content.AttributionSource;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioRecord;
@@ -22,24 +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();
try {
fillActivityThread();
FakeContext.get();
} catch (Exception e) {
throw new AssertionError(e);
}
boolean mustFillAppInfo = false;
boolean mustFillBaseContext = false;
boolean mustFillAppContext = false;
if (Build.BRAND.equalsIgnoreCase("meizu")) {
@@ -60,7 +69,6 @@ public final class Workarounds {
// - <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1595382142>
// - <https://github.com/Genymobile/scrcpy/issues/3805#issuecomment-1596148031>
mustFillAppInfo = true;
mustFillBaseContext = true;
mustFillAppContext = true;
}
@@ -73,15 +81,12 @@ public final class Workarounds {
if (camera) {
mustFillAppInfo = true;
mustFillBaseContext = true;
mustFillAppContext = true;
}
if (mustFillAppInfo) {
Workarounds.fillAppInfo();
}
if (mustFillBaseContext) {
Workarounds.fillBaseContext();
}
if (mustFillAppContext) {
Workarounds.fillAppContext();
}
@@ -100,27 +105,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();
@@ -130,26 +117,15 @@ public final class Workarounds {
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.packageName = FakeContext.PACKAGE_NAME;
Application application = new Application() {
@Override
public String getOpPackageName() {
return FakeContext.PACKAGE_NAME;
}
};
Field initialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
initialApplicationField.setAccessible(true);
initialApplicationField.set(activityThread, application);
// appBindData.appInfo = applicationInfo;
Field appInfoField = appBindDataClass.getDeclaredField("appInfo");
appInfoField.setAccessible(true);
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());
@@ -159,33 +135,29 @@ public final class Workarounds {
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
private static void fillAppContext() {
try {
fillActivityThread();
Application app = Application.class.newInstance();
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;
}
}
@@ -324,16 +296,4 @@ public final class Workarounds {
throw new RuntimeException("Cannot create AudioRecord");
}
}
static Context retrieveSystemContext() {
try {
Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext");
Context ctx = (Context) getSystemContextMethod.invoke(activityThread);
Ln.i("===== " + ctx);
return ctx;
} catch (Exception e) {
Ln.e("Cannot retrieve system context", e);
return null;
}
}
}