Compare commits

..

15 Commits

Author SHA1 Message Date
Romain Vimont
dedbc3f8cd Recover on invalid camera FPS ranges
Some devices may provide invalid ranges, causing an
IllegalArgumentException "lower must be less than or equal to upper".

Catch the exception to list the cameras anyway.

Refs #4403 <https://github.com/Genymobile/scrcpy/issues/4403>
2023-11-05 15:14:04 +01:00
Romain Vimont
ad8ced5b1e Log physical cameras 2023-11-05 12:16:14 +01:00
Romain Vimont
36b17ee068 Simplify --list-cameras ouptut
Remove --video-source=camera from the output of --list-cameras (this is
implicit).
2023-11-05 11:52: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
Romain Vimont
446ea818a4 Update links to v2.2 2023-11-01 18:47:58 +01:00
Romain Vimont
c3c7bf7af3 Bump version to v2.2 2023-11-01 18:36:33 +01:00
Romain Vimont
5000368c2f Merge branch 'master' into release 2023-11-01 18:36:13 +01:00
Romain Vimont
855ae4adb1 Upgrade SDL (2.28.4) for Windows
Include the latest version of SDL in Windows releases.
2023-11-01 18:36:10 +01:00
Romain Vimont
a8db3ec9e2 Upgrade platform-tools (34.0.5) for Windows
Include the latest version of adb in Windows releases.
2023-11-01 18:36:10 +01:00
Romain Vimont
ff579990c2 Shutdown connection before joining threads
Interrupting async processors may require to shutdown the connection to
wake up blocking calls.

Therefore, shutdown the connection first, then join the threads, then
close the connection.

Refs commit 9c08eb79cb
2023-11-01 18:36:10 +01:00
Romain Vimont
b8c5853aa6 Disable default stdout/stderr
Some devices (mostly Xiaomi) print internal errors using
e.printStackTrace(), flooding the console with irrelevant errors.

Disable system streams used via System.out and System.err streams, to
print only the logs from scrcpy.

Refs #994 <https://github.com/Genymobile/scrcpy/issues/994>
Refs #4213 <https://github.com/Genymobile/scrcpy/pull/4213>
2023-11-01 18:36:04 +01:00
AmirSina Mashayekh
90ca46ee41 Add scrcpy-server to .gitignore
The script install_release.sh downloads a file named scrcpy-server to
the repo root directory. Add it to .gitignore so that it is ignored.

PR #4364 <https://github.com/Genymobile/scrcpy/pull/4364>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-10-21 09:01:50 +02:00
20 changed files with 157 additions and 165 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ build/
.gradle/
/x/
local.properties
/scrcpy-server

View File

@@ -1,4 +1,4 @@
# scrcpy (v2.1.1)
# scrcpy (v2.2)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View File

@@ -6,10 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=platform-tools-34.0.3
DEP_DIR=platform-tools-34.0.5
FILENAME=platform-tools_r34.0.3-windows.zip
SHA256SUM=fce992e93eb786fc9f47df93d83a7b912c46742d45c39d712c02e06d05b72e2b
FILENAME=platform-tools_r34.0.5-windows.zip
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
if [[ -d "$DEP_DIR" ]]
then

View File

@@ -6,10 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=SDL2-2.28.0
DEP_DIR=SDL2-2.28.4
FILENAME=SDL2-devel-2.28.0-mingw.tar.gz
SHA256SUM=b91ce59eeacd4a9db403f976fd2337d9360b21ada374124417d716065c380e20
FILENAME=SDL2-devel-2.28.4-mingw.tar.gz
SHA256SUM=779d091072cf97291f80030f5232d97aa3d48ab0f2c14fe0b9d9a33c593cdc35
if [[ -d "$DEP_DIR" ]]
then

View File

@@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "2.1.1"
VALUE "ProductVersion", "v2.2"
END
END
BLOCK "VarFileInfo"

View File

@@ -17,5 +17,5 @@ endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win32'
prebuilt_sdl2 = 'SDL2-2.28.0/i686-w64-mingw32'
prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32'

View File

@@ -17,5 +17,5 @@ endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win64'
prebuilt_sdl2 = 'SDL2-2.28.0/x86_64-w64-mingw32'
prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64'

View File

@@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v2.1.1`][direct-scrcpy-server]
<sub>SHA-256: `9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e`</sub>
- [`scrcpy-server-v2.2`][direct-scrcpy-server]
<sub>SHA-256: `c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@@ -4,14 +4,14 @@
Download the [latest release]:
- [`scrcpy-win64-v2.1.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `f77281e1bce2f9934617699c581f063d5b327f012eff602ee98fb2ef550c25c2`</sub>
- [`scrcpy-win32-v2.1.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `ef7ae7fbe9449f2643febdc2244fb186d1a746a3c736394150cfd14f06d3c943`</sub>
- [`scrcpy-win64-v2.2.zip`][direct-win64] (64-bit)
<sub>SHA-256: `9f9da88ac4c8319dcb9bf852f2d9bba942bac663413383419cddf64eaa5685bd`</sub>
- [`scrcpy-win32-v2.2.zip`][direct-win32] (32-bit)
<sub>SHA-256: `cb84269fc847b8b880e320879492a1ae6c017b42175f03e199530f7a53be9d74`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win64-v2.1.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win32-v2.1.1.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win64-v2.2.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win32-v2.2.zip
and extract it.

View File

@@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1
PREBUILT_SERVER_SHA256=9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2
PREBUILT_SERVER_SHA256=c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '2.1.1',
version: 'v2.2',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@@ -98,10 +98,10 @@ dist-win32: build-server build-win32
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.28.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64
@@ -116,10 +116,10 @@ dist-win64: build-server build-win64
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.28.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 33
versionCode 20101
versionName "2.1.1"
versionCode 200
versionName "v2.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.1.1
SCRCPY_VERSION_NAME=v2.2
PLATFORM=${ANDROID_PLATFORM:-33}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}

View File

@@ -132,20 +132,29 @@ public final class DesktopConnection implements Closeable {
return controlSocket;
}
public void close() throws IOException {
public void shutdown() throws IOException {
if (videoSocket != null) {
videoSocket.shutdownInput();
videoSocket.shutdownOutput();
videoSocket.close();
}
if (audioSocket != null) {
audioSocket.shutdownInput();
audioSocket.shutdownOutput();
audioSocket.close();
}
if (controlSocket != null) {
controlSocket.shutdownInput();
controlSocket.shutdownOutput();
}
}
public void close() throws IOException {
if (videoSocket != null) {
videoSocket.close();
}
if (audioSocket != null) {
audioSocket.close();
}
if (controlSocket != null) {
controlSocket.close();
}
}

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(null);
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

@@ -2,33 +2,37 @@ package com.genymobile.scrcpy;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
/**
* Log both to Android logger (so that logs are visible in "adb logcat") and standard output/error (so that they are visible in the terminal
* directly).
*/
public final class Ln {
public interface ConsolePrinter {
void printOut(String message);
void printErr(String message, Throwable throwable);
}
private static final String TAG = "scrcpy";
private static final String PREFIX = "[server] ";
private static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out));
private static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err));
enum Level {
VERBOSE, DEBUG, INFO, WARN, ERROR
}
private static ConsolePrinter consolePrinter = new DefaultConsolePrinter();
private static Level threshold = Level.INFO;
private Ln() {
// not instantiable
}
public static void setConsolePrinter(ConsolePrinter consolePrinter) {
Ln.consolePrinter = consolePrinter;
public static void disableSystemStreams() {
PrintStream nullStream = new PrintStream(new NullOutputStream());
System.setOut(nullStream);
System.setErr(nullStream);
}
/**
@@ -49,28 +53,31 @@ public final class Ln {
public static void v(String message) {
if (isEnabled(Level.VERBOSE)) {
Log.v(TAG, message);
consolePrinter.printOut(PREFIX + "VERBOSE: " + message + '\n');
CONSOLE_OUT.print(PREFIX + "VERBOSE: " + message + '\n');
}
}
public static void d(String message) {
if (isEnabled(Level.DEBUG)) {
Log.d(TAG, message);
consolePrinter.printOut(PREFIX + "DEBUG: " + message + '\n');
CONSOLE_OUT.print(PREFIX + "DEBUG: " + message + '\n');
}
}
public static void i(String message) {
if (isEnabled(Level.INFO)) {
Log.i(TAG, message);
consolePrinter.printOut(PREFIX + "INFO: " + message + '\n');
CONSOLE_OUT.print(PREFIX + "INFO: " + message + '\n');
}
}
public static void w(String message, Throwable throwable) {
if (isEnabled(Level.WARN)) {
Log.w(TAG, message, throwable);
consolePrinter.printErr(PREFIX + "WARN: " + message + '\n', throwable);
CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n');
if (throwable != null) {
throwable.printStackTrace(CONSOLE_ERR);
}
}
}
@@ -81,7 +88,10 @@ public final class Ln {
public static void e(String message, Throwable throwable) {
if (isEnabled(Level.ERROR)) {
Log.e(TAG, message, throwable);
consolePrinter.printErr(PREFIX + "ERROR: " + message + '\n', throwable);
CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n');
if (throwable != null) {
throwable.printStackTrace(CONSOLE_ERR);
}
}
}
@@ -89,18 +99,20 @@ public final class Ln {
e(message, null);
}
public static class DefaultConsolePrinter implements ConsolePrinter {
static class NullOutputStream extends OutputStream {
@Override
public void printOut(String message) {
System.out.print(message);
public void write(byte[] b) {
// ignore
}
@Override
public void printErr(String message, Throwable throwable) {
System.err.print(message);
if (throwable != null) {
throwable.printStackTrace();
}
public void write(byte[] b, int off, int len) {
// ignore
}
@Override
public void write(int b) {
// ignore
}
}
}

View File

@@ -3,14 +3,17 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.DisplayManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.annotation.TargetApi;
import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaCodec;
import android.os.Build;
import android.util.Range;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -84,6 +87,7 @@ public final class LogUtils {
}
}
@TargetApi(Build.VERSION_CODES.P)
public static String buildCameraListMessage(boolean includeSizes) {
StringBuilder builder = new StringBuilder("List of cameras:");
CameraManager cameraManager = ServiceManager.getCameraManager();
@@ -93,19 +97,35 @@ 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);
builder.append(" (").append(getCameraFacingName(facing)).append(", ");
Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
builder.append(activeSize.width()).append("x").append(activeSize.height()).append(", ");
builder.append(activeSize.width()).append("x").append(activeSize.height());
// Capture frame rates for low-FPS mode are the same for every resolution
Range<Integer>[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
SortedSet<Integer> uniqueLowFps = getUniqueSet(lowFpsRanges);
builder.append("fps=").append(uniqueLowFps).append(')');
try {
// Capture frame rates for low-FPS mode are the same for every resolution
Range<Integer>[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
SortedSet<Integer> uniqueLowFps = getUniqueSet(lowFpsRanges);
builder.append(", fps=").append(uniqueLowFps);
} catch (Exception e) {
// Some devices may provide invalid ranges, causing an IllegalArgumentException "lower must be less than or equal to upper"
Ln.w("Could not get available frame rates for camera " + id, e);
}
builder.append(')');
int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
if (Arrays.asList(capabilities).contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) {
builder.append("\n Logical multi-camera, backed by physical cameras:");
for (String phyId : characteristics.getPhysicalCameraIds()) {
CameraCharacteristics phyCharacteristics = cameraManager.getCameraCharacteristics(phyId);
appendPhysicalCamera(builder, phyId, phyCharacteristics);
}
}
if (includeSizes) {
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
@@ -134,6 +154,13 @@ public final class LogUtils {
return builder.toString();
}
private static void appendPhysicalCamera(StringBuilder builder, String id, CameraCharacteristics characteristics) {
builder.append("\n --camera-id=").append(id).append(" (");
Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
builder.append(activeSize.width()).append("x").append(activeSize.height()).append(")");
}
private static SortedSet<Integer> getUniqueSet(Range<Integer>[] ranges) {
SortedSet<Integer> set = new TreeSet<>();
for (Range<Integer> range : ranges) {

View File

@@ -163,6 +163,8 @@ public final class Server {
asyncProcessor.stop();
}
connection.shutdown();
try {
initThread.join();
for (AsyncProcessor asyncProcessor : asyncProcessors) {
@@ -187,7 +189,7 @@ public final class Server {
try {
internalMain(args);
} catch (Throwable t) {
t.printStackTrace();
Ln.e(t.getMessage(), t);
status = 1;
} finally {
// By default, the Java process exits when all non-daemon threads are terminated.
@@ -204,6 +206,7 @@ public final class Server {
Options options = Options.parse(args);
Ln.disableSystemStreams();
Ln.initLogLevel(options.getLogLevel());
Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");

View File

@@ -14,12 +14,6 @@ import android.os.Build;
import android.os.Looper;
import android.os.Parcel;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -27,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")) {
@@ -59,14 +69,7 @@ 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;
} else if (Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) {
// Trash the messages printed to the console by direct calls to System.out and System.err.
// Xiaomi device ROMs may print internal errors using e.printStackTrace(), flooding the console with irrelevant errors.
ExclusiveConsolePrinter exclusiveConsolePrinter = new ExclusiveConsolePrinter();
exclusiveConsolePrinter.installNullSystemStreams();
Ln.setConsolePrinter(new ExclusiveConsolePrinter());
}
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
@@ -78,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();
}
@@ -105,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();
@@ -141,9 +123,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());
@@ -153,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;
}
}
@@ -318,48 +296,4 @@ public final class Workarounds {
throw new RuntimeException("Cannot create AudioRecord");
}
}
static class ExclusiveConsolePrinter implements Ln.ConsolePrinter {
static class NullOutputStream extends OutputStream {
@Override
public void write(byte[] b) {
// ignore
}
@Override
public void write(byte[] b, int off, int len) {
// ignore
}
@Override
public void write(int b) {
// ignore
}
}
private final PrintStream realOut = new PrintStream(new FileOutputStream(FileDescriptor.out));
private final PrintStream realErr = new PrintStream(new FileOutputStream(FileDescriptor.err));
void installNullSystemStreams() {
PrintStream nullStream = new PrintStream(new NullOutputStream());
System.setOut(nullStream);
System.setErr(nullStream);
}
@Override
public void printOut(String message) {
realOut.print(message);
}
@Override
public void printErr(String message, Throwable throwable) {
realErr.print(message);
if (throwable != null) {
StringWriter errors = new StringWriter();
throwable.printStackTrace(new PrintWriter(errors));
realErr.print(errors);
}
}
}
}