diff --git a/QtScrcpy/QtScrcpyCore b/QtScrcpy/QtScrcpyCore
index fa40b6ac9..769943161 160000
--- a/QtScrcpy/QtScrcpyCore
+++ b/QtScrcpy/QtScrcpyCore
@@ -1 +1 @@
-Subproject commit fa40b6ac9d683a39f7cc002f2f49c6c18ecb4981
+Subproject commit 769943161f99dbc7b0c55f7f769e32729ab06693
diff --git a/config/config.ini b/config/config.ini
index df5b23583..0b8a1f8e0 100644
--- a/config/config.ini
+++ b/config/config.ini
@@ -10,7 +10,7 @@ RenderExpiredFrames=0
# 视频解码方式:-1 自动,0 软解,1 dx硬解,2 opengl硬解
UseDesktopOpenGL=-1
# scrcpy-server的版本号(不要修改)
-ServerVersion=2.1.1
+ServerVersion=2.4
# scrcpy-server推送到安卓设备的路径
ServerPath=/data/local/tmp/scrcpy-server.jar
# 自定义adb路径,例如D:/android/tools/adb.exe
diff --git a/server/.gitignore b/server/.gitignore
deleted file mode 100644
index 0df7064d6..000000000
--- a/server/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/
-.DS_Store
-/build
-/captures
-.externalNativeBuild
diff --git a/server/build.gradle b/server/build.gradle
deleted file mode 100644
index dbc8261f6..000000000
--- a/server/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 31
- defaultConfig {
- applicationId "com.genymobile.scrcpy"
- minSdkVersion 21
- targetSdkVersion 31
- versionCode 12400
- versionName "1.24"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- testImplementation 'junit:junit:4.13.1'
-}
-
-apply from: "$project.rootDir/config/android-checkstyle.gradle"
diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh
deleted file mode 100755
index c881e38a8..000000000
--- a/server/build_without_gradle.sh
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/env bash
-#
-# This script generates the scrcpy binary "manually" (without gradle).
-#
-# Adapt Android platform and build tools versions (via ANDROID_PLATFORM and
-# ANDROID_BUILD_TOOLS environment variables).
-#
-# Then execute:
-#
-# BUILD_DIR=my_build_dir ./build_without_gradle.sh
-
-set -e
-
-SCRCPY_DEBUG=false
-SCRCPY_VERSION_NAME=1.24
-
-PLATFORM=${ANDROID_PLATFORM:-31}
-BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
-
-BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
-CLASSES_DIR="$BUILD_DIR/classes"
-SERVER_DIR=$(dirname "$0")
-SERVER_BINARY=scrcpy-server
-ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
-
-echo "Platform: android-$PLATFORM"
-echo "Build-tools: $BUILD_TOOLS"
-echo "Build dir: $BUILD_DIR"
-
-rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
-mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
-
-<< EOF cat > "$CLASSES_DIR/com/genymobile/scrcpy/BuildConfig.java"
-package com.genymobile.scrcpy;
-
-public final class BuildConfig {
- public static final boolean DEBUG = $SCRCPY_DEBUG;
- public static final String VERSION_NAME = "$SCRCPY_VERSION_NAME";
-}
-EOF
-
-echo "Generating java from aidl..."
-cd "$SERVER_DIR/src/main/aidl"
-"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
- android/view/IRotationWatcher.aidl
-"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
- android/content/IOnPrimaryClipChangedListener.aidl
-
-echo "Compiling java sources..."
-cd ../java
-javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \
- -source 1.8 -target 1.8 \
- com/genymobile/scrcpy/*.java \
- com/genymobile/scrcpy/wrappers/*.java
-
-echo "Dexing..."
-cd "$CLASSES_DIR"
-
-if [[ $PLATFORM -lt 31 ]]
-then
- # use dx
- "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
- --output "$BUILD_DIR/classes.dex" \
- android/view/*.class \
- android/content/*.class \
- com/genymobile/scrcpy/*.class \
- com/genymobile/scrcpy/wrappers/*.class
-
- echo "Archiving..."
- cd "$BUILD_DIR"
- jar cvf "$SERVER_BINARY" classes.dex
- rm -rf classes.dex classes
-else
- # use d8
- "$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
- --output "$BUILD_DIR/classes.zip" \
- android/view/*.class \
- android/content/*.class \
- com/genymobile/scrcpy/*.class \
- com/genymobile/scrcpy/wrappers/*.class
-
- cd "$BUILD_DIR"
- mv classes.zip "$SERVER_BINARY"
- rm -rf classes
-fi
-
-echo "Server generated in $BUILD_DIR/$SERVER_BINARY"
diff --git a/server/meson.build b/server/meson.build
deleted file mode 100644
index 984daf3b2..000000000
--- a/server/meson.build
+++ /dev/null
@@ -1,25 +0,0 @@
-# It may be useful to use a prebuilt server, so that no Android SDK is required
-# to build. If the 'prebuilt_server' option is set, just copy the file as is.
-prebuilt_server = get_option('prebuilt_server')
-if prebuilt_server == ''
- custom_target('scrcpy-server',
- # gradle is responsible for tracking source changes
- build_by_default: true,
- build_always_stale: true,
- output: 'scrcpy-server',
- command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
- console: true,
- install: true,
- install_dir: 'share/scrcpy')
-else
- if not prebuilt_server.startswith('/')
- # relative path needs some trick
- prebuilt_server = meson.source_root() + '/' + prebuilt_server
- endif
- custom_target('scrcpy-server-prebuilt',
- input: prebuilt_server,
- output: 'scrcpy-server',
- command: ['cp', '@INPUT@', '@OUTPUT@'],
- install: true,
- install_dir: 'share/scrcpy')
-endif
diff --git a/server/proguard-rules.pro b/server/proguard-rules.pro
deleted file mode 100644
index f1b424510..000000000
--- a/server/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/server/scripts/build-wrapper.sh b/server/scripts/build-wrapper.sh
deleted file mode 100644
index 7e16dc946..000000000
--- a/server/scripts/build-wrapper.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env bash
-# Wrapper script to invoke gradle from meson
-set -e
-
-# Do not execute gradle when ninja is called as root (it would download the
-# whole gradle world in /root/.gradle).
-# This is typically useful for calling "sudo ninja install" after a "ninja
-# install"
-if [[ "$EUID" == 0 ]]
-then
- echo "(not invoking gradle, since we are root)" >&2
- exit 0
-fi
-
-PROJECT_ROOT="$1"
-OUTPUT="$2"
-BUILDTYPE="$3"
-
-# gradlew is in the parent of the server directory
-GRADLE=${GRADLE:-$PROJECT_ROOT/../gradlew}
-
-if [[ "$BUILDTYPE" == debug ]]
-then
- "$GRADLE" -p "$PROJECT_ROOT" assembleDebug
- cp "$PROJECT_ROOT/build/outputs/apk/debug/server-debug.apk" "$OUTPUT"
-else
- "$GRADLE" -p "$PROJECT_ROOT" assembleRelease
- cp "$PROJECT_ROOT/build/outputs/apk/release/server-release-unsigned.apk" "$OUTPUT"
-fi
diff --git a/server/src/main/AndroidManifest.xml b/server/src/main/AndroidManifest.xml
deleted file mode 100644
index ccd69d2f3..000000000
--- a/server/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl b/server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl
deleted file mode 100644
index 46d7f7cab..000000000
--- a/server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-/**
- * {@hide}
- */
-oneway interface IOnPrimaryClipChangedListener {
- void dispatchPrimaryClipChanged();
-}
diff --git a/server/src/main/aidl/android/view/IRotationWatcher.aidl b/server/src/main/aidl/android/view/IRotationWatcher.aidl
deleted file mode 100644
index 2cc5e44ac..000000000
--- a/server/src/main/aidl/android/view/IRotationWatcher.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/* //device/java/android/android/hardware/ISensorListener.aidl
-**
-** Copyright 2008, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package android.view;
-
-/**
- * {@hide}
- */
-interface IRotationWatcher {
- oneway void onRotationChanged(int rotation);
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java
deleted file mode 100644
index 319a957d1..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java
+++ /dev/null
@@ -1,197 +0,0 @@
-package com.genymobile.scrcpy;
-
-import com.genymobile.scrcpy.wrappers.ServiceManager;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Base64;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Handle the cleanup of scrcpy, even if the main process is killed.
- *
- * This is useful to restore some state when scrcpy is closed, even on device disconnection (which kills the scrcpy process).
- */
-public final class CleanUp {
-
- public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
-
- // A simple struct to be passed from the main process to the cleanup process
- public static class Config implements Parcelable {
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public Config createFromParcel(Parcel in) {
- return new Config(in);
- }
-
- @Override
- public Config[] newArray(int size) {
- return new Config[size];
- }
- };
-
- private static final int FLAG_DISABLE_SHOW_TOUCHES = 1;
- private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
- private static final int FLAG_POWER_OFF_SCREEN = 4;
-
- private int displayId;
-
- // Restore the value (between 0 and 7), -1 to not restore
- //
- private int restoreStayOn = -1;
-
- private boolean disableShowTouches;
- private boolean restoreNormalPowerMode;
- private boolean powerOffScreen;
-
- public Config() {
- // Default constructor, the fields are initialized by CleanUp.configure()
- }
-
- protected Config(Parcel in) {
- displayId = in.readInt();
- restoreStayOn = in.readInt();
- byte options = in.readByte();
- disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0;
- restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0;
- powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(displayId);
- dest.writeInt(restoreStayOn);
- byte options = 0;
- if (disableShowTouches) {
- options |= FLAG_DISABLE_SHOW_TOUCHES;
- }
- if (restoreNormalPowerMode) {
- options |= FLAG_RESTORE_NORMAL_POWER_MODE;
- }
- if (powerOffScreen) {
- options |= FLAG_POWER_OFF_SCREEN;
- }
- dest.writeByte(options);
- }
-
- private boolean hasWork() {
- return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- byte[] serialize() {
- Parcel parcel = Parcel.obtain();
- writeToParcel(parcel, 0);
- byte[] bytes = parcel.marshall();
- parcel.recycle();
- return bytes;
- }
-
- static Config deserialize(byte[] bytes) {
- Parcel parcel = Parcel.obtain();
- parcel.unmarshall(bytes, 0, bytes.length);
- parcel.setDataPosition(0);
- return CREATOR.createFromParcel(parcel);
- }
-
- static Config fromBase64(String base64) {
- byte[] bytes = Base64.decode(base64, Base64.NO_WRAP);
- return deserialize(bytes);
- }
-
- String toBase64() {
- byte[] bytes = serialize();
- return Base64.encodeToString(bytes, Base64.NO_WRAP);
- }
- }
-
- private CleanUp() {
- // not instantiable
- }
-
- public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
- throws IOException {
- Config config = new Config();
- config.displayId = displayId;
- config.disableShowTouches = disableShowTouches;
- config.restoreStayOn = restoreStayOn;
- config.restoreNormalPowerMode = restoreNormalPowerMode;
- config.powerOffScreen = powerOffScreen;
-
- if (config.hasWork()) {
- startProcess(config);
- } else {
- // There is no additional clean up to do when scrcpy dies
- unlinkSelf();
- }
- }
-
- private static void startProcess(Config config) throws IOException {
- String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
-
- ProcessBuilder builder = new ProcessBuilder(cmd);
- builder.environment().put("CLASSPATH", SERVER_PATH);
- builder.start();
- }
-
- private static void unlinkSelf() {
- try {
- new File(SERVER_PATH).delete();
- } catch (Exception e) {
- Ln.e("Could not unlink server", e);
- }
- }
-
- public static void main(String... args) {
- unlinkSelf();
-
- try {
- // Wait for the server to die
- System.in.read();
- } catch (IOException e) {
- // Expected when the server is dead
- }
-
- Ln.i("Cleaning up");
-
- Config config = Config.fromBase64(args[0]);
-
- if (config.disableShowTouches || config.restoreStayOn != -1) {
- ServiceManager serviceManager = new ServiceManager();
- Settings settings = new Settings(serviceManager);
- if (config.disableShowTouches) {
- Ln.i("Disabling \"show touches\"");
- try {
- settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
- } catch (SettingsException e) {
- Ln.e("Could not restore \"show_touches\"", e);
- }
- }
- if (config.restoreStayOn != -1) {
- Ln.i("Restoring \"stay awake\"");
- try {
- settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
- } catch (SettingsException e) {
- Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
- }
- }
- }
-
- if (Device.isScreenOn()) {
- if (config.powerOffScreen) {
- Ln.i("Power off screen");
- Device.powerOffScreen(config.displayId);
- } else if (config.restoreNormalPowerMode) {
- Ln.i("Restoring normal power mode");
- Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
- }
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecOption.java b/server/src/main/java/com/genymobile/scrcpy/CodecOption.java
deleted file mode 100644
index 12f2a8899..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/CodecOption.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class CodecOption {
- private String key;
- private Object value;
-
- public CodecOption(String key, Object value) {
- this.key = key;
- this.value = value;
- }
-
- public String getKey() {
- return key;
- }
-
- public Object getValue() {
- return value;
- }
-
- public static List parse(String codecOptions) {
- if (codecOptions.isEmpty()) {
- return null;
- }
-
- List result = new ArrayList<>();
-
- boolean escape = false;
- StringBuilder buf = new StringBuilder();
-
- for (char c : codecOptions.toCharArray()) {
- switch (c) {
- case '\\':
- if (escape) {
- buf.append('\\');
- escape = false;
- } else {
- escape = true;
- }
- break;
- case ',':
- if (escape) {
- buf.append(',');
- escape = false;
- } else {
- // This comma is a separator between codec options
- String codecOption = buf.toString();
- result.add(parseOption(codecOption));
- // Clear buf
- buf.setLength(0);
- }
- break;
- default:
- buf.append(c);
- break;
- }
- }
-
- if (buf.length() > 0) {
- String codecOption = buf.toString();
- result.add(parseOption(codecOption));
- }
-
- return result;
- }
-
- private static CodecOption parseOption(String option) {
- int equalSignIndex = option.indexOf('=');
- if (equalSignIndex == -1) {
- throw new IllegalArgumentException("'=' expected");
- }
- String keyAndType = option.substring(0, equalSignIndex);
- if (keyAndType.length() == 0) {
- throw new IllegalArgumentException("Key may not be null");
- }
-
- String key;
- String type;
-
- int colonIndex = keyAndType.indexOf(':');
- if (colonIndex != -1) {
- key = keyAndType.substring(0, colonIndex);
- type = keyAndType.substring(colonIndex + 1);
- } else {
- key = keyAndType;
- type = "int"; // assume int by default
- }
-
- Object value;
- String valueString = option.substring(equalSignIndex + 1);
- switch (type) {
- case "int":
- value = Integer.parseInt(valueString);
- break;
- case "long":
- value = Long.parseLong(valueString);
- break;
- case "float":
- value = Float.parseFloat(valueString);
- break;
- case "string":
- value = valueString;
- break;
- default:
- throw new IllegalArgumentException("Invalid codec option type (int, long, float, str): " + type);
- }
-
- return new CodecOption(key, value);
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Command.java b/server/src/main/java/com/genymobile/scrcpy/Command.java
deleted file mode 100644
index 0ef976a66..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Command.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Scanner;
-
-public final class Command {
- private Command() {
- // not instantiable
- }
-
- public static void exec(String... cmd) throws IOException, InterruptedException {
- Process process = Runtime.getRuntime().exec(cmd);
- int exitCode = process.waitFor();
- if (exitCode != 0) {
- throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
- }
- }
-
- public static String execReadLine(String... cmd) throws IOException, InterruptedException {
- String result = null;
- Process process = Runtime.getRuntime().exec(cmd);
- Scanner scanner = new Scanner(process.getInputStream());
- if (scanner.hasNextLine()) {
- result = scanner.nextLine();
- }
- int exitCode = process.waitFor();
- if (exitCode != 0) {
- throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
- }
- return result;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java
deleted file mode 100644
index 99eb805f2..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package com.genymobile.scrcpy;
-
-/**
- * Union of all supported event types, identified by their {@code type}.
- */
-public final class ControlMessage {
-
- public static final int TYPE_INJECT_KEYCODE = 0;
- public static final int TYPE_INJECT_TEXT = 1;
- public static final int TYPE_INJECT_TOUCH_EVENT = 2;
- public static final int TYPE_INJECT_SCROLL_EVENT = 3;
- public static final int TYPE_BACK_OR_SCREEN_ON = 4;
- public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
- public static final int TYPE_EXPAND_SETTINGS_PANEL = 6;
- public static final int TYPE_COLLAPSE_PANELS = 7;
- public static final int TYPE_GET_CLIPBOARD = 8;
- public static final int TYPE_SET_CLIPBOARD = 9;
- public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
- public static final int TYPE_ROTATE_DEVICE = 11;
-
- public static final long SEQUENCE_INVALID = 0;
-
- public static final int COPY_KEY_NONE = 0;
- public static final int COPY_KEY_COPY = 1;
- public static final int COPY_KEY_CUT = 2;
-
- private int type;
- private String text;
- private int metaState; // KeyEvent.META_*
- private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
- private int keycode; // KeyEvent.KEYCODE_*
- private int buttons; // MotionEvent.BUTTON_*
- private long pointerId;
- private float pressure;
- private Position position;
- private int hScroll;
- private int vScroll;
- private int copyKey;
- private boolean paste;
- private int repeat;
- private long sequence;
-
- private ControlMessage() {
- }
-
- public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_INJECT_KEYCODE;
- msg.action = action;
- msg.keycode = keycode;
- msg.repeat = repeat;
- msg.metaState = metaState;
- return msg;
- }
-
- public static ControlMessage createInjectText(String text) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_INJECT_TEXT;
- msg.text = text;
- return msg;
- }
-
- public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure, int buttons) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_INJECT_TOUCH_EVENT;
- msg.action = action;
- msg.pointerId = pointerId;
- msg.pressure = pressure;
- msg.position = position;
- msg.buttons = buttons;
- return msg;
- }
-
- public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_INJECT_SCROLL_EVENT;
- msg.position = position;
- msg.hScroll = hScroll;
- msg.vScroll = vScroll;
- msg.buttons = buttons;
- return msg;
- }
-
- public static ControlMessage createBackOrScreenOn(int action) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_BACK_OR_SCREEN_ON;
- msg.action = action;
- return msg;
- }
-
- public static ControlMessage createGetClipboard(int copyKey) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_GET_CLIPBOARD;
- msg.copyKey = copyKey;
- return msg;
- }
-
- public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_SET_CLIPBOARD;
- msg.sequence = sequence;
- msg.text = text;
- msg.paste = paste;
- return msg;
- }
-
- /**
- * @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
- */
- public static ControlMessage createSetScreenPowerMode(int mode) {
- ControlMessage msg = new ControlMessage();
- msg.type = TYPE_SET_SCREEN_POWER_MODE;
- msg.action = mode;
- return msg;
- }
-
- public static ControlMessage createEmpty(int type) {
- ControlMessage msg = new ControlMessage();
- msg.type = type;
- return msg;
- }
-
- public int getType() {
- return type;
- }
-
- public String getText() {
- return text;
- }
-
- public int getMetaState() {
- return metaState;
- }
-
- public int getAction() {
- return action;
- }
-
- public int getKeycode() {
- return keycode;
- }
-
- public int getButtons() {
- return buttons;
- }
-
- public long getPointerId() {
- return pointerId;
- }
-
- public float getPressure() {
- return pressure;
- }
-
- public Position getPosition() {
- return position;
- }
-
- public int getHScroll() {
- return hScroll;
- }
-
- public int getVScroll() {
- return vScroll;
- }
-
- public int getCopyKey() {
- return copyKey;
- }
-
- public boolean getPaste() {
- return paste;
- }
-
- public int getRepeat() {
- return repeat;
- }
-
- public long getSequence() {
- return sequence;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java
deleted file mode 100644
index 24dc5e50e..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-
-public class ControlMessageReader {
-
- static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
- static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
- static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24;
- static final int BACK_OR_SCREEN_ON_LENGTH = 1;
- static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
- static final int GET_CLIPBOARD_LENGTH = 1;
- static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
-
- private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
-
- public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
- public static final int INJECT_TEXT_MAX_LENGTH = 300;
-
- private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
- private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
-
- public ControlMessageReader() {
- // invariant: the buffer is always in "get" mode
- buffer.limit(0);
- }
-
- public boolean isFull() {
- return buffer.remaining() == rawBuffer.length;
- }
-
- public void readFrom(InputStream input) throws IOException {
- if (isFull()) {
- throw new IllegalStateException("Buffer full, call next() to consume");
- }
- buffer.compact();
- int head = buffer.position();
- int r = input.read(rawBuffer, head, rawBuffer.length - head);
- if (r == -1) {
- throw new EOFException("Controller socket closed");
- }
- buffer.position(head + r);
- buffer.flip();
- }
-
- public ControlMessage next() {
- if (!buffer.hasRemaining()) {
- return null;
- }
- int savedPosition = buffer.position();
-
- int type = buffer.get();
- ControlMessage msg;
- switch (type) {
- case ControlMessage.TYPE_INJECT_KEYCODE:
- msg = parseInjectKeycode();
- break;
- case ControlMessage.TYPE_INJECT_TEXT:
- msg = parseInjectText();
- break;
- case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
- msg = parseInjectTouchEvent();
- break;
- case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
- msg = parseInjectScrollEvent();
- break;
- case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
- msg = parseBackOrScreenOnEvent();
- break;
- case ControlMessage.TYPE_GET_CLIPBOARD:
- msg = parseGetClipboard();
- break;
- case ControlMessage.TYPE_SET_CLIPBOARD:
- msg = parseSetClipboard();
- break;
- case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
- msg = parseSetScreenPowerMode();
- break;
- case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
- case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
- case ControlMessage.TYPE_COLLAPSE_PANELS:
- case ControlMessage.TYPE_ROTATE_DEVICE:
- msg = ControlMessage.createEmpty(type);
- break;
- default:
- Ln.w("Unknown event type: " + type);
- msg = null;
- break;
- }
-
- if (msg == null) {
- // failure, reset savedPosition
- buffer.position(savedPosition);
- }
- return msg;
- }
-
- private ControlMessage parseInjectKeycode() {
- if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
- return null;
- }
- int action = toUnsigned(buffer.get());
- int keycode = buffer.getInt();
- int repeat = buffer.getInt();
- int metaState = buffer.getInt();
- return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
- }
-
- private String parseString() {
- if (buffer.remaining() < 4) {
- return null;
- }
- int len = buffer.getInt();
- if (buffer.remaining() < len) {
- return null;
- }
- int position = buffer.position();
- // Move the buffer position to consume the text
- buffer.position(position + len);
- return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
- }
-
- private ControlMessage parseInjectText() {
- String text = parseString();
- if (text == null) {
- return null;
- }
- return ControlMessage.createInjectText(text);
- }
-
- private ControlMessage parseInjectTouchEvent() {
- if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
- return null;
- }
- int action = toUnsigned(buffer.get());
- long pointerId = buffer.getLong();
- Position position = readPosition(buffer);
- // 16 bits fixed-point
- int pressureInt = toUnsigned(buffer.getShort());
- // convert it to a float between 0 and 1 (0x1p16f is 2^16 as float)
- float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f);
- int buttons = buffer.getInt();
- return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);
- }
-
- private ControlMessage parseInjectScrollEvent() {
- if (buffer.remaining() < INJECT_SCROLL_EVENT_PAYLOAD_LENGTH) {
- return null;
- }
- Position position = readPosition(buffer);
- int hScroll = buffer.getInt();
- int vScroll = buffer.getInt();
- int buttons = buffer.getInt();
- return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
- }
-
- private ControlMessage parseBackOrScreenOnEvent() {
- if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
- return null;
- }
- int action = toUnsigned(buffer.get());
- return ControlMessage.createBackOrScreenOn(action);
- }
-
- private ControlMessage parseGetClipboard() {
- if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
- return null;
- }
- int copyKey = toUnsigned(buffer.get());
- return ControlMessage.createGetClipboard(copyKey);
- }
-
- private ControlMessage parseSetClipboard() {
- if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
- return null;
- }
- long sequence = buffer.getLong();
- boolean paste = buffer.get() != 0;
- String text = parseString();
- if (text == null) {
- return null;
- }
- return ControlMessage.createSetClipboard(sequence, text, paste);
- }
-
- private ControlMessage parseSetScreenPowerMode() {
- if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) {
- return null;
- }
- int mode = buffer.get();
- return ControlMessage.createSetScreenPowerMode(mode);
- }
-
- private static Position readPosition(ByteBuffer buffer) {
- int x = buffer.getInt();
- int y = buffer.getInt();
- int screenWidth = toUnsigned(buffer.getShort());
- int screenHeight = toUnsigned(buffer.getShort());
- return new Position(x, y, screenWidth, screenHeight);
- }
-
- private static int toUnsigned(short value) {
- return value & 0xffff;
- }
-
- private static int toUnsigned(byte value) {
- return value & 0xff;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java
deleted file mode 100644
index 913371ee0..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Controller.java
+++ /dev/null
@@ -1,317 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.os.Build;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.io.IOException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public class Controller {
-
- private static final int DEFAULT_DEVICE_ID = 0;
-
- private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
-
- private final Device device;
- private final DesktopConnection connection;
- private final DeviceMessageSender sender;
- private final boolean clipboardAutosync;
- private final boolean powerOn;
-
- private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-
- private long lastTouchDown;
- private final PointersState pointersState = new PointersState();
- private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
- private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
-
- private boolean keepPowerModeOff;
-
- public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) {
- this.device = device;
- this.connection = connection;
- this.clipboardAutosync = clipboardAutosync;
- this.powerOn = powerOn;
- initPointers();
- sender = new DeviceMessageSender(connection);
- }
-
- private void initPointers() {
- for (int i = 0; i < PointersState.MAX_POINTERS; ++i) {
- MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
- props.toolType = MotionEvent.TOOL_TYPE_FINGER;
-
- MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
- coords.orientation = 0;
- coords.size = 0;
-
- pointerProperties[i] = props;
- pointerCoords[i] = coords;
- }
- }
-
- public void control() throws IOException {
- // on start, power on the device
- if (powerOn && !Device.isScreenOn()) {
- device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
-
- // dirty hack
- // After POWER is injected, the device is powered on asynchronously.
- // To turn the device screen off while mirroring, the client will send a message that
- // would be handled before the device is actually powered on, so its effect would
- // be "canceled" once the device is turned back on.
- // Adding this delay prevents to handle the message before the device is actually
- // powered on.
- SystemClock.sleep(500);
- }
-
- while (true) {
- handleEvent();
- }
- }
-
- public DeviceMessageSender getSender() {
- return sender;
- }
-
- private void handleEvent() throws IOException {
- ControlMessage msg = connection.receiveControlMessage();
- switch (msg.getType()) {
- case ControlMessage.TYPE_INJECT_KEYCODE:
- if (device.supportsInputEvents()) {
- injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
- }
- break;
- case ControlMessage.TYPE_INJECT_TEXT:
- if (device.supportsInputEvents()) {
- injectText(msg.getText());
- }
- break;
- case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
- if (device.supportsInputEvents()) {
- injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
- }
- break;
- case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
- if (device.supportsInputEvents()) {
- injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons());
- }
- break;
- case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
- if (device.supportsInputEvents()) {
- pressBackOrTurnScreenOn(msg.getAction());
- }
- break;
- case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
- Device.expandNotificationPanel();
- break;
- case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
- Device.expandSettingsPanel();
- break;
- case ControlMessage.TYPE_COLLAPSE_PANELS:
- Device.collapsePanels();
- break;
- case ControlMessage.TYPE_GET_CLIPBOARD:
- getClipboard(msg.getCopyKey());
- break;
- case ControlMessage.TYPE_SET_CLIPBOARD:
- setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
- break;
- case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
- if (device.supportsInputEvents()) {
- int mode = msg.getAction();
- boolean setPowerModeOk = Device.setScreenPowerMode(mode);
- if (setPowerModeOk) {
- keepPowerModeOff = mode == Device.POWER_MODE_OFF;
- Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
- }
- }
- break;
- case ControlMessage.TYPE_ROTATE_DEVICE:
- Device.rotateDevice();
- break;
- default:
- // do nothing
- }
- }
-
- private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
- if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
- schedulePowerModeOff();
- }
- return device.injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC);
- }
-
- private boolean injectChar(char c) {
- String decomposed = KeyComposition.decompose(c);
- char[] chars = decomposed != null ? decomposed.toCharArray() : new char[]{c};
- KeyEvent[] events = charMap.getEvents(chars);
- if (events == null) {
- return false;
- }
- for (KeyEvent event : events) {
- if (!device.injectEvent(event, Device.INJECT_MODE_ASYNC)) {
- return false;
- }
- }
- return true;
- }
-
- private int injectText(String text) {
- int successCount = 0;
- for (char c : text.toCharArray()) {
- if (!injectChar(c)) {
- Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
- continue;
- }
- successCount++;
- }
- return successCount;
- }
-
- private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
- long now = SystemClock.uptimeMillis();
-
- Point point = device.getPhysicalPoint(position);
- if (point == null) {
- Ln.w("Ignore touch event, it was generated for a different device size");
- return false;
- }
-
- int pointerIndex = pointersState.getPointerIndex(pointerId);
- if (pointerIndex == -1) {
- Ln.w("Too many pointers for touch event");
- return false;
- }
- Pointer pointer = pointersState.get(pointerIndex);
- pointer.setPoint(point);
- pointer.setPressure(pressure);
- pointer.setUp(action == MotionEvent.ACTION_UP);
-
- int pointerCount = pointersState.update(pointerProperties, pointerCoords);
-
- if (pointerCount == 1) {
- if (action == MotionEvent.ACTION_DOWN) {
- lastTouchDown = now;
- }
- } else {
- // secondary pointers must use ACTION_POINTER_* ORed with the pointerIndex
- if (action == MotionEvent.ACTION_UP) {
- action = MotionEvent.ACTION_POINTER_UP | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
- } else if (action == MotionEvent.ACTION_DOWN) {
- action = MotionEvent.ACTION_POINTER_DOWN | (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
- }
- }
-
- // Right-click and middle-click only work if the source is a mouse
- boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
- int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
- if (source != InputDevice.SOURCE_MOUSE) {
- // Buttons must not be set for touch events
- buttons = 0;
- }
-
- MotionEvent event = MotionEvent
- .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
- 0);
- return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
- }
-
- private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) {
- long now = SystemClock.uptimeMillis();
- Point point = device.getPhysicalPoint(position);
- if (point == null) {
- // ignore event
- return false;
- }
-
- MotionEvent.PointerProperties props = pointerProperties[0];
- props.id = 0;
-
- MotionEvent.PointerCoords coords = pointerCoords[0];
- coords.x = point.getX();
- coords.y = point.getY();
- coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
- coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
-
- MotionEvent event = MotionEvent
- .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0,
- InputDevice.SOURCE_MOUSE, 0);
- return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
- }
-
- /**
- * Schedule a call to set power mode to off after a small delay.
- */
- private static void schedulePowerModeOff() {
- EXECUTOR.schedule(new Runnable() {
- @Override
- public void run() {
- Ln.i("Forcing screen off");
- Device.setScreenPowerMode(Device.POWER_MODE_OFF);
- }
- }, 200, TimeUnit.MILLISECONDS);
- }
-
- private boolean pressBackOrTurnScreenOn(int action) {
- if (Device.isScreenOn()) {
- return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
- }
-
- // Screen is off
- // Only press POWER on ACTION_DOWN
- if (action != KeyEvent.ACTION_DOWN) {
- // do nothing,
- return true;
- }
-
- if (keepPowerModeOff) {
- schedulePowerModeOff();
- }
- return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
- }
-
- private void getClipboard(int copyKey) {
- // On Android >= 7, press the COPY or CUT key if requested
- if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
- int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT;
- // Wait until the event is finished, to ensure that the clipboard text we read just after is the correct one
- device.pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH);
- }
-
- // If clipboard autosync is enabled, then the device clipboard is synchronized to the computer clipboard whenever it changes, in
- // particular when COPY or CUT are injected, so it should not be synchronized twice. On Android < 7, do not synchronize at all rather than
- // copying an old clipboard content.
- if (!clipboardAutosync) {
- String clipboardText = Device.getClipboardText();
- if (clipboardText != null) {
- sender.pushClipboardText(clipboardText);
- }
- }
- }
-
- private boolean setClipboard(String text, boolean paste, long sequence) {
- boolean ok = device.setClipboardText(text);
- if (ok) {
- Ln.i("Device clipboard set");
- }
-
- // On Android >= 7, also press the PASTE key if requested
- if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
- device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC);
- }
-
- if (sequence != ControlMessage.SEQUENCE_INVALID) {
- // Acknowledgement requested
- sender.pushAckClipboard(sequence);
- }
-
- return ok;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java
deleted file mode 100644
index 78728d81e..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-
-import java.io.Closeable;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-public final class DesktopConnection implements Closeable {
-
- private static final int DEVICE_NAME_FIELD_LENGTH = 64;
-
- private static final String SOCKET_NAME = "scrcpy";
-
- private final LocalSocket videoSocket;
- private final FileDescriptor videoFd;
-
- private final LocalSocket controlSocket;
- private final InputStream controlInputStream;
- private final OutputStream controlOutputStream;
-
- private final ControlMessageReader reader = new ControlMessageReader();
- private final DeviceMessageWriter writer = new DeviceMessageWriter();
-
- private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
- this.videoSocket = videoSocket;
- this.controlSocket = controlSocket;
- if (controlSocket != null) {
- controlInputStream = controlSocket.getInputStream();
- controlOutputStream = controlSocket.getOutputStream();
- } else {
- controlInputStream = null;
- controlOutputStream = null;
- }
- videoFd = videoSocket.getFileDescriptor();
- }
-
- private static LocalSocket connect(String abstractName) throws IOException {
- LocalSocket localSocket = new LocalSocket();
- localSocket.connect(new LocalSocketAddress(abstractName));
- return localSocket;
- }
-
- public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException {
- LocalSocket videoSocket;
- LocalSocket controlSocket = null;
- if (tunnelForward) {
- LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
- try {
- videoSocket = localServerSocket.accept();
- if (sendDummyByte) {
- // send one byte so the client may read() to detect a connection error
- videoSocket.getOutputStream().write(0);
- }
- if (control) {
- try {
- controlSocket = localServerSocket.accept();
- } catch (IOException | RuntimeException e) {
- videoSocket.close();
- throw e;
- }
- }
- } finally {
- localServerSocket.close();
- }
- } else {
- videoSocket = connect(SOCKET_NAME);
- if (control) {
- try {
- controlSocket = connect(SOCKET_NAME);
- } catch (IOException | RuntimeException e) {
- videoSocket.close();
- throw e;
- }
- }
- }
-
- return new DesktopConnection(videoSocket, controlSocket);
- }
-
- public void close() throws IOException {
- videoSocket.shutdownInput();
- videoSocket.shutdownOutput();
- videoSocket.close();
- if (controlSocket != null) {
- controlSocket.shutdownInput();
- controlSocket.shutdownOutput();
- controlSocket.close();
- }
- }
-
- public void sendDeviceMeta(String deviceName, int width, int height) throws IOException {
- byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
-
- byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
- int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1);
- System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
- // byte[] are always 0-initialized in java, no need to set '\0' explicitly
-
- buffer[DEVICE_NAME_FIELD_LENGTH] = (byte) (width >> 8);
- buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
- buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
- buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
- IO.writeFully(videoFd, buffer, 0, buffer.length);
- }
-
- public FileDescriptor getVideoFd() {
- return videoFd;
- }
-
- public ControlMessage receiveControlMessage() throws IOException {
- ControlMessage msg = reader.next();
- while (msg == null) {
- reader.readFrom(controlInputStream);
- msg = reader.next();
- }
- return msg;
- }
-
- public void sendDeviceMessage(DeviceMessage msg) throws IOException {
- writer.writeTo(msg, controlOutputStream);
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java
deleted file mode 100644
index 763a7fadb..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Device.java
+++ /dev/null
@@ -1,322 +0,0 @@
-package com.genymobile.scrcpy;
-
-import com.genymobile.scrcpy.wrappers.ClipboardManager;
-import com.genymobile.scrcpy.wrappers.InputManager;
-import com.genymobile.scrcpy.wrappers.ServiceManager;
-import com.genymobile.scrcpy.wrappers.SurfaceControl;
-import com.genymobile.scrcpy.wrappers.WindowManager;
-
-import android.content.IOnPrimaryClipChangedListener;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.SystemClock;
-import android.view.IRotationWatcher;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public final class Device {
-
- public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
- public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
-
- public static final int INJECT_MODE_ASYNC = InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
- public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
- public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
-
- public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
- public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
-
- private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
- private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);
-
- public interface RotationListener {
- void onRotationChanged(int rotation);
- }
-
- public interface ClipboardListener {
- void onClipboardTextChanged(String text);
- }
-
- private final Size deviceSize;
- private final Rect crop;
- private int maxSize;
- private final int lockVideoOrientation;
-
- private ScreenInfo screenInfo;
- private RotationListener rotationListener;
- private ClipboardListener clipboardListener;
- private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
-
- /**
- * Logical display identifier
- */
- private final int displayId;
-
- /**
- * The surface flinger layer stack associated with this logical display
- */
- private final int layerStack;
-
- private final boolean supportsInputEvents;
-
- public Device(Options options) {
- displayId = options.getDisplayId();
- DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId);
- if (displayInfo == null) {
- int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds();
- throw new InvalidDisplayIdException(displayId, displayIds);
- }
-
- int displayInfoFlags = displayInfo.getFlags();
-
- deviceSize = displayInfo.getSize();
- crop = options.getCrop();
- maxSize = options.getMaxSize();
- lockVideoOrientation = options.getLockVideoOrientation();
-
- screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
- layerStack = displayInfo.getLayerStack();
-
- SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
- @Override
- public void onRotationChanged(int rotation) {
- synchronized (Device.this) {
- screenInfo = screenInfo.withDeviceRotation(rotation);
-
- // notify
- if (rotationListener != null) {
- rotationListener.onRotationChanged(rotation);
- }
- }
- }
- }, displayId);
-
- if (options.getControl() && options.getClipboardAutosync()) {
- // If control and autosync are enabled, synchronize Android clipboard to the computer automatically
- ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
- if (clipboardManager != null) {
- clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
- @Override
- public void dispatchPrimaryClipChanged() {
- if (isSettingClipboard.get()) {
- // This is a notification for the change we are currently applying, ignore it
- return;
- }
- synchronized (Device.this) {
- if (clipboardListener != null) {
- String text = getClipboardText();
- if (text != null) {
- clipboardListener.onClipboardTextChanged(text);
- }
- }
- }
- }
- });
- } else {
- Ln.w("No clipboard manager, copy-paste between device and computer will not work");
- }
- }
-
- if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
- Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
- }
-
- // main display or any display on Android >= Q
- supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
- if (!supportsInputEvents) {
- Ln.w("Input events are not supported for secondary displays before Android 10");
- }
- }
-
- public synchronized void setMaxSize(int newMaxSize) {
- maxSize = newMaxSize;
- screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
- }
-
- public synchronized ScreenInfo getScreenInfo() {
- return screenInfo;
- }
-
- public int getLayerStack() {
- return layerStack;
- }
-
- public Point getPhysicalPoint(Position position) {
- // it hides the field on purpose, to read it with a lock
- @SuppressWarnings("checkstyle:HiddenField")
- ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
-
- // ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation
- Size unlockedVideoSize = screenInfo.getUnlockedVideoSize();
-
- int reverseVideoRotation = screenInfo.getReverseVideoRotation();
- // reverse the video rotation to apply the events
- Position devicePosition = position.rotate(reverseVideoRotation);
-
- Size clientVideoSize = devicePosition.getScreenSize();
- if (!unlockedVideoSize.equals(clientVideoSize)) {
- // The client sends a click relative to a video with wrong dimensions,
- // the device may have been rotated since the event was generated, so ignore the event
- return null;
- }
- Rect contentRect = screenInfo.getContentRect();
- Point point = devicePosition.getPoint();
- int convertedX = contentRect.left + point.getX() * contentRect.width() / unlockedVideoSize.getWidth();
- int convertedY = contentRect.top + point.getY() * contentRect.height() / unlockedVideoSize.getHeight();
- return new Point(convertedX, convertedY);
- }
-
- public static String getDeviceName() {
- return Build.MODEL;
- }
-
- public static boolean supportsInputEvents(int displayId) {
- return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
- }
-
- public boolean supportsInputEvents() {
- return supportsInputEvents;
- }
-
- public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) {
- if (!supportsInputEvents(displayId)) {
- throw new AssertionError("Could not inject input event if !supportsInputEvents()");
- }
-
- if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
- return false;
- }
-
- return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode);
- }
-
- public boolean injectEvent(InputEvent event, int injectMode) {
- return injectEvent(event, displayId, injectMode);
- }
-
- public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId, int injectMode) {
- long now = SystemClock.uptimeMillis();
- KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
- InputDevice.SOURCE_KEYBOARD);
- return injectEvent(event, displayId, injectMode);
- }
-
- public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) {
- return injectKeyEvent(action, keyCode, repeat, metaState, displayId, injectMode);
- }
-
- public static boolean pressReleaseKeycode(int keyCode, int displayId, int injectMode) {
- return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId, injectMode)
- && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
- }
-
- public boolean pressReleaseKeycode(int keyCode, int injectMode) {
- return pressReleaseKeycode(keyCode, displayId, injectMode);
- }
-
- public static boolean isScreenOn() {
- return SERVICE_MANAGER.getPowerManager().isScreenOn();
- }
-
- public synchronized void setRotationListener(RotationListener rotationListener) {
- this.rotationListener = rotationListener;
- }
-
- public synchronized void setClipboardListener(ClipboardListener clipboardListener) {
- this.clipboardListener = clipboardListener;
- }
-
- public static void expandNotificationPanel() {
- SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
- }
-
- public static void expandSettingsPanel() {
- SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
- }
-
- public static void collapsePanels() {
- SERVICE_MANAGER.getStatusBarManager().collapsePanels();
- }
-
- public static String getClipboardText() {
- ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
- if (clipboardManager == null) {
- return null;
- }
- CharSequence s = clipboardManager.getText();
- if (s == null) {
- return null;
- }
- return s.toString();
- }
-
- public boolean setClipboardText(String text) {
- ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
- if (clipboardManager == null) {
- return false;
- }
-
- String currentClipboard = getClipboardText();
- if (currentClipboard != null && currentClipboard.equals(text)) {
- // The clipboard already contains the requested text.
- // Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
- // the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
- // problem, do not explicitly set the clipboard text if it already contains the expected content.
- return false;
- }
-
- isSettingClipboard.set(true);
- boolean ok = clipboardManager.setText(text);
- isSettingClipboard.set(false);
- return ok;
- }
-
- /**
- * @param mode one of the {@code POWER_MODE_*} constants
- */
- public static boolean setScreenPowerMode(int mode) {
- IBinder d = SurfaceControl.getBuiltInDisplay();
- if (d == null) {
- Ln.e("Could not get built-in display");
- return false;
- }
- return SurfaceControl.setDisplayPowerMode(d, mode);
- }
-
- public static boolean powerOffScreen(int displayId) {
- if (!isScreenOn()) {
- return true;
- }
- return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
- }
-
- /**
- * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
- */
- public static void rotateDevice() {
- WindowManager wm = SERVICE_MANAGER.getWindowManager();
-
- boolean accelerometerRotation = !wm.isRotationFrozen();
-
- int currentRotation = wm.getRotation();
- int newRotation = (currentRotation & 1) ^ 1; // 0->1, 1->0, 2->1, 3->0
- String newRotationString = newRotation == 0 ? "portrait" : "landscape";
-
- Ln.i("Device rotation requested: " + newRotationString);
- wm.freezeRotation(newRotation);
-
- // restore auto-rotate if necessary
- if (accelerometerRotation) {
- wm.thawRotation();
- }
- }
-
- public static Settings getSettings() {
- return SETTINGS;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java
deleted file mode 100644
index 5b7c4de5b..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.genymobile.scrcpy;
-
-public final class DeviceMessage {
-
- public static final int TYPE_CLIPBOARD = 0;
- public static final int TYPE_ACK_CLIPBOARD = 1;
-
- public static final long SEQUENCE_INVALID = ControlMessage.SEQUENCE_INVALID;
-
- private int type;
- private String text;
- private long sequence;
-
- private DeviceMessage() {
- }
-
- public static DeviceMessage createClipboard(String text) {
- DeviceMessage event = new DeviceMessage();
- event.type = TYPE_CLIPBOARD;
- event.text = text;
- return event;
- }
-
- public static DeviceMessage createAckClipboard(long sequence) {
- DeviceMessage event = new DeviceMessage();
- event.type = TYPE_ACK_CLIPBOARD;
- event.sequence = sequence;
- return event;
- }
-
- public int getType() {
- return type;
- }
-
- public String getText() {
- return text;
- }
-
- public long getSequence() {
- return sequence;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java
deleted file mode 100644
index 4ebccaccf..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.io.IOException;
-
-public final class DeviceMessageSender {
-
- private final DesktopConnection connection;
-
- private String clipboardText;
-
- private long ack;
-
- public DeviceMessageSender(DesktopConnection connection) {
- this.connection = connection;
- }
-
- public synchronized void pushClipboardText(String text) {
- clipboardText = text;
- notify();
- }
-
- public synchronized void pushAckClipboard(long sequence) {
- ack = sequence;
- notify();
- }
-
- public void loop() throws IOException, InterruptedException {
- while (true) {
- String text;
- long sequence;
- synchronized (this) {
- while (ack == DeviceMessage.SEQUENCE_INVALID && clipboardText == null) {
- wait();
- }
- text = clipboardText;
- clipboardText = null;
-
- sequence = ack;
- ack = DeviceMessage.SEQUENCE_INVALID;
- }
-
- if (sequence != DeviceMessage.SEQUENCE_INVALID) {
- DeviceMessage event = DeviceMessage.createAckClipboard(sequence);
- connection.sendDeviceMessage(event);
- }
- if (text != null) {
- DeviceMessage event = DeviceMessage.createClipboard(text);
- connection.sendDeviceMessage(event);
- }
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java
deleted file mode 100644
index bcd8d2067..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-
-public class DeviceMessageWriter {
-
- private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
- public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
-
- private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
- private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
-
- public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
- buffer.clear();
- buffer.put((byte) msg.getType());
- switch (msg.getType()) {
- case DeviceMessage.TYPE_CLIPBOARD:
- String text = msg.getText();
- byte[] raw = text.getBytes(StandardCharsets.UTF_8);
- int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
- buffer.putInt(len);
- buffer.put(raw, 0, len);
- output.write(rawBuffer, 0, buffer.position());
- break;
- case DeviceMessage.TYPE_ACK_CLIPBOARD:
- buffer.putLong(msg.getSequence());
- output.write(rawBuffer, 0, buffer.position());
- break;
- default:
- Ln.w("Unknown device message: " + msg.getType());
- break;
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java b/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java
deleted file mode 100644
index 4b8036f85..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/DisplayInfo.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.genymobile.scrcpy;
-
-public final class DisplayInfo {
- private final int displayId;
- private final Size size;
- private final int rotation;
- private final int layerStack;
- private final int flags;
-
- public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001;
-
- public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) {
- this.displayId = displayId;
- this.size = size;
- this.rotation = rotation;
- this.layerStack = layerStack;
- this.flags = flags;
- }
-
- public int getDisplayId() {
- return displayId;
- }
-
- public Size getSize() {
- return size;
- }
-
- public int getRotation() {
- return rotation;
- }
-
- public int getLayerStack() {
- return layerStack;
- }
-
- public int getFlags() {
- return flags;
- }
-}
-
diff --git a/server/src/main/java/com/genymobile/scrcpy/IO.java b/server/src/main/java/com/genymobile/scrcpy/IO.java
deleted file mode 100644
index 57c017dbe..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/IO.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-public final class IO {
- private IO() {
- // not instantiable
- }
-
- public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
- // ByteBuffer position is not updated as expected by Os.write() on old Android versions, so
- // count the remaining bytes manually.
- // See .
- int remaining = from.remaining();
- while (remaining > 0) {
- try {
- int w = Os.write(fd, from);
- if (BuildConfig.DEBUG && w < 0) {
- // w should not be negative, since an exception is thrown on error
- throw new AssertionError("Os.write() returned a negative value (" + w + ")");
- }
- remaining -= w;
- } catch (ErrnoException e) {
- if (e.errno != OsConstants.EINTR) {
- throw new IOException(e);
- }
- }
- }
- }
-
- public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
- writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java b/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java
deleted file mode 100644
index 81e3b9037..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.genymobile.scrcpy;
-
-public class InvalidDisplayIdException extends RuntimeException {
-
- private final int displayId;
- private final int[] availableDisplayIds;
-
- public InvalidDisplayIdException(int displayId, int[] availableDisplayIds) {
- super("There is no display having id " + displayId);
- this.displayId = displayId;
- this.availableDisplayIds = availableDisplayIds;
- }
-
- public int getDisplayId() {
- return displayId;
- }
-
- public int[] getAvailableDisplayIds() {
- return availableDisplayIds;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java b/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java
deleted file mode 100644
index 1efd2989d..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.media.MediaCodecInfo;
-
-public class InvalidEncoderException extends RuntimeException {
-
- private final String name;
- private final MediaCodecInfo[] availableEncoders;
-
- public InvalidEncoderException(String name, MediaCodecInfo[] availableEncoders) {
- super("There is no encoder having name '" + name + '"');
- this.name = name;
- this.availableEncoders = availableEncoders;
- }
-
- public String getName() {
- return name;
- }
-
- public MediaCodecInfo[] getAvailableEncoders() {
- return availableEncoders;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/KeyComposition.java b/server/src/main/java/com/genymobile/scrcpy/KeyComposition.java
deleted file mode 100644
index 2f2835c95..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/KeyComposition.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Decompose accented characters.
- *
- * For example, {@link #decompose(char) decompose('é')} returns {@code "\u0301e"}.
- *
- * This is useful for injecting key events to generate the expected character ({@link android.view.KeyCharacterMap#getEvents(char[])}
- * KeyCharacterMap.getEvents()} returns {@code null} with input {@code "é"} but works with input {@code "\u0301e"}).
- *
- * See diacritical dead key characters.
- */
-public final class KeyComposition {
-
- private static final String KEY_DEAD_GRAVE = "\u0300";
- private static final String KEY_DEAD_ACUTE = "\u0301";
- private static final String KEY_DEAD_CIRCUMFLEX = "\u0302";
- private static final String KEY_DEAD_TILDE = "\u0303";
- private static final String KEY_DEAD_UMLAUT = "\u0308";
-
- private static final Map COMPOSITION_MAP = createDecompositionMap();
-
- private KeyComposition() {
- // not instantiable
- }
-
- public static String decompose(char c) {
- return COMPOSITION_MAP.get(c);
- }
-
- private static String grave(char c) {
- return KEY_DEAD_GRAVE + c;
- }
-
- private static String acute(char c) {
- return KEY_DEAD_ACUTE + c;
- }
-
- private static String circumflex(char c) {
- return KEY_DEAD_CIRCUMFLEX + c;
- }
-
- private static String tilde(char c) {
- return KEY_DEAD_TILDE + c;
- }
-
- private static String umlaut(char c) {
- return KEY_DEAD_UMLAUT + c;
- }
-
- private static Map createDecompositionMap() {
- Map map = new HashMap<>();
- map.put('À', grave('A'));
- map.put('È', grave('E'));
- map.put('Ì', grave('I'));
- map.put('Ò', grave('O'));
- map.put('Ù', grave('U'));
- map.put('à', grave('a'));
- map.put('è', grave('e'));
- map.put('ì', grave('i'));
- map.put('ò', grave('o'));
- map.put('ù', grave('u'));
- map.put('Ǹ', grave('N'));
- map.put('ǹ', grave('n'));
- map.put('Ẁ', grave('W'));
- map.put('ẁ', grave('w'));
- map.put('Ỳ', grave('Y'));
- map.put('ỳ', grave('y'));
-
- map.put('Á', acute('A'));
- map.put('É', acute('E'));
- map.put('Í', acute('I'));
- map.put('Ó', acute('O'));
- map.put('Ú', acute('U'));
- map.put('Ý', acute('Y'));
- map.put('á', acute('a'));
- map.put('é', acute('e'));
- map.put('í', acute('i'));
- map.put('ó', acute('o'));
- map.put('ú', acute('u'));
- map.put('ý', acute('y'));
- map.put('Ć', acute('C'));
- map.put('ć', acute('c'));
- map.put('Ĺ', acute('L'));
- map.put('ĺ', acute('l'));
- map.put('Ń', acute('N'));
- map.put('ń', acute('n'));
- map.put('Ŕ', acute('R'));
- map.put('ŕ', acute('r'));
- map.put('Ś', acute('S'));
- map.put('ś', acute('s'));
- map.put('Ź', acute('Z'));
- map.put('ź', acute('z'));
- map.put('Ǵ', acute('G'));
- map.put('ǵ', acute('g'));
- map.put('Ḉ', acute('Ç'));
- map.put('ḉ', acute('ç'));
- map.put('Ḱ', acute('K'));
- map.put('ḱ', acute('k'));
- map.put('Ḿ', acute('M'));
- map.put('ḿ', acute('m'));
- map.put('Ṕ', acute('P'));
- map.put('ṕ', acute('p'));
- map.put('Ẃ', acute('W'));
- map.put('ẃ', acute('w'));
-
- map.put('Â', circumflex('A'));
- map.put('Ê', circumflex('E'));
- map.put('Î', circumflex('I'));
- map.put('Ô', circumflex('O'));
- map.put('Û', circumflex('U'));
- map.put('â', circumflex('a'));
- map.put('ê', circumflex('e'));
- map.put('î', circumflex('i'));
- map.put('ô', circumflex('o'));
- map.put('û', circumflex('u'));
- map.put('Ĉ', circumflex('C'));
- map.put('ĉ', circumflex('c'));
- map.put('Ĝ', circumflex('G'));
- map.put('ĝ', circumflex('g'));
- map.put('Ĥ', circumflex('H'));
- map.put('ĥ', circumflex('h'));
- map.put('Ĵ', circumflex('J'));
- map.put('ĵ', circumflex('j'));
- map.put('Ŝ', circumflex('S'));
- map.put('ŝ', circumflex('s'));
- map.put('Ŵ', circumflex('W'));
- map.put('ŵ', circumflex('w'));
- map.put('Ŷ', circumflex('Y'));
- map.put('ŷ', circumflex('y'));
- map.put('Ẑ', circumflex('Z'));
- map.put('ẑ', circumflex('z'));
-
- map.put('Ã', tilde('A'));
- map.put('Ñ', tilde('N'));
- map.put('Õ', tilde('O'));
- map.put('ã', tilde('a'));
- map.put('ñ', tilde('n'));
- map.put('õ', tilde('o'));
- map.put('Ĩ', tilde('I'));
- map.put('ĩ', tilde('i'));
- map.put('Ũ', tilde('U'));
- map.put('ũ', tilde('u'));
- map.put('Ẽ', tilde('E'));
- map.put('ẽ', tilde('e'));
- map.put('Ỹ', tilde('Y'));
- map.put('ỹ', tilde('y'));
-
- map.put('Ä', umlaut('A'));
- map.put('Ë', umlaut('E'));
- map.put('Ï', umlaut('I'));
- map.put('Ö', umlaut('O'));
- map.put('Ü', umlaut('U'));
- map.put('ä', umlaut('a'));
- map.put('ë', umlaut('e'));
- map.put('ï', umlaut('i'));
- map.put('ö', umlaut('o'));
- map.put('ü', umlaut('u'));
- map.put('ÿ', umlaut('y'));
- map.put('Ÿ', umlaut('Y'));
- map.put('Ḧ', umlaut('H'));
- map.put('ḧ', umlaut('h'));
- map.put('Ẅ', umlaut('W'));
- map.put('ẅ', umlaut('w'));
- map.put('Ẍ', umlaut('X'));
- map.put('ẍ', umlaut('x'));
- map.put('ẗ', umlaut('t'));
-
- return map;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java
deleted file mode 100644
index c39fc621c..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Ln.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.util.Log;
-
-/**
- * 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 {
-
- private static final String TAG = "scrcpy";
- private static final String PREFIX = "[server] ";
-
- enum Level {
- VERBOSE, DEBUG, INFO, WARN, ERROR
- }
-
- private static Level threshold = Level.INFO;
-
- private Ln() {
- // not instantiable
- }
-
- /**
- * Initialize the log level.
- *
- * Must be called before starting any new thread.
- *
- * @param level the log level
- */
- public static void initLogLevel(Level level) {
- threshold = level;
- }
-
- public static boolean isEnabled(Level level) {
- return level.ordinal() >= threshold.ordinal();
- }
-
- public static void v(String message) {
- if (isEnabled(Level.VERBOSE)) {
- Log.v(TAG, message);
- System.out.println(PREFIX + "VERBOSE: " + message);
- }
- }
-
- public static void d(String message) {
- if (isEnabled(Level.DEBUG)) {
- Log.d(TAG, message);
- System.out.println(PREFIX + "DEBUG: " + message);
- }
- }
-
- public static void i(String message) {
- if (isEnabled(Level.INFO)) {
- Log.i(TAG, message);
- System.out.println(PREFIX + "INFO: " + message);
- }
- }
-
- public static void w(String message, Throwable throwable) {
- if (isEnabled(Level.WARN)) {
- Log.w(TAG, message, throwable);
- System.out.println(PREFIX + "WARN: " + message);
- if (throwable != null) {
- throwable.printStackTrace();
- }
- }
- }
-
- public static void w(String message) {
- w(message, null);
- }
-
- public static void e(String message, Throwable throwable) {
- if (isEnabled(Level.ERROR)) {
- Log.e(TAG, message, throwable);
- System.out.println(PREFIX + "ERROR: " + message);
- if (throwable != null) {
- throwable.printStackTrace();
- }
- }
- }
-
- public static void e(String message) {
- e(message, null);
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java
deleted file mode 100644
index d1607c200..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Options.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.graphics.Rect;
-
-import java.util.List;
-
-public class Options {
- private Ln.Level logLevel = Ln.Level.DEBUG;
- private int maxSize;
- private int bitRate = 8000000;
- private int maxFps;
- private int lockVideoOrientation = -1;
- private boolean tunnelForward;
- private Rect crop;
- private boolean control = true;
- private int displayId;
- private boolean showTouches;
- private boolean stayAwake;
- private List codecOptions;
- private String encoderName;
- private boolean powerOffScreenOnClose;
- private boolean clipboardAutosync = true;
- private boolean downsizeOnError = true;
- private boolean cleanup = true;
- private boolean powerOn = true;
-
- // Options not used by the scrcpy client, but useful to use scrcpy-server directly
- private boolean sendDeviceMeta = true; // send device name and size
- private boolean sendFrameMeta = true; // send PTS so that the client may record properly
- private boolean sendDummyByte = true; // write a byte on start to detect connection issues
-
- public Ln.Level getLogLevel() {
- return logLevel;
- }
-
- public void setLogLevel(Ln.Level logLevel) {
- this.logLevel = logLevel;
- }
-
- public int getMaxSize() {
- return maxSize;
- }
-
- public void setMaxSize(int maxSize) {
- this.maxSize = maxSize;
- }
-
- public int getBitRate() {
- return bitRate;
- }
-
- public void setBitRate(int bitRate) {
- this.bitRate = bitRate;
- }
-
- public int getMaxFps() {
- return maxFps;
- }
-
- public void setMaxFps(int maxFps) {
- this.maxFps = maxFps;
- }
-
- public int getLockVideoOrientation() {
- return lockVideoOrientation;
- }
-
- public void setLockVideoOrientation(int lockVideoOrientation) {
- this.lockVideoOrientation = lockVideoOrientation;
- }
-
- public boolean isTunnelForward() {
- return tunnelForward;
- }
-
- public void setTunnelForward(boolean tunnelForward) {
- this.tunnelForward = tunnelForward;
- }
-
- public Rect getCrop() {
- return crop;
- }
-
- public void setCrop(Rect crop) {
- this.crop = crop;
- }
-
- public boolean getControl() {
- return control;
- }
-
- public void setControl(boolean control) {
- this.control = control;
- }
-
- public int getDisplayId() {
- return displayId;
- }
-
- public void setDisplayId(int displayId) {
- this.displayId = displayId;
- }
-
- public boolean getShowTouches() {
- return showTouches;
- }
-
- public void setShowTouches(boolean showTouches) {
- this.showTouches = showTouches;
- }
-
- public boolean getStayAwake() {
- return stayAwake;
- }
-
- public void setStayAwake(boolean stayAwake) {
- this.stayAwake = stayAwake;
- }
-
- public List getCodecOptions() {
- return codecOptions;
- }
-
- public void setCodecOptions(List codecOptions) {
- this.codecOptions = codecOptions;
- }
-
- public String getEncoderName() {
- return encoderName;
- }
-
- public void setEncoderName(String encoderName) {
- this.encoderName = encoderName;
- }
-
- public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
- this.powerOffScreenOnClose = powerOffScreenOnClose;
- }
-
- public boolean getPowerOffScreenOnClose() {
- return this.powerOffScreenOnClose;
- }
-
- public boolean getClipboardAutosync() {
- return clipboardAutosync;
- }
-
- public void setClipboardAutosync(boolean clipboardAutosync) {
- this.clipboardAutosync = clipboardAutosync;
- }
-
- public boolean getDownsizeOnError() {
- return downsizeOnError;
- }
-
- public void setDownsizeOnError(boolean downsizeOnError) {
- this.downsizeOnError = downsizeOnError;
- }
-
- public boolean getCleanup() {
- return cleanup;
- }
-
- public void setCleanup(boolean cleanup) {
- this.cleanup = cleanup;
- }
-
- public boolean getPowerOn() {
- return powerOn;
- }
-
- public void setPowerOn(boolean powerOn) {
- this.powerOn = powerOn;
- }
-
- public boolean getSendDeviceMeta() {
- return sendDeviceMeta;
- }
-
- public void setSendDeviceMeta(boolean sendDeviceMeta) {
- this.sendDeviceMeta = sendDeviceMeta;
- }
-
- public boolean getSendFrameMeta() {
- return sendFrameMeta;
- }
-
- public void setSendFrameMeta(boolean sendFrameMeta) {
- this.sendFrameMeta = sendFrameMeta;
- }
-
- public boolean getSendDummyByte() {
- return sendDummyByte;
- }
-
- public void setSendDummyByte(boolean sendDummyByte) {
- this.sendDummyByte = sendDummyByte;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Point.java b/server/src/main/java/com/genymobile/scrcpy/Point.java
deleted file mode 100644
index c2a30fa81..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Point.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.util.Objects;
-
-public class Point {
- private final int x;
- private final int y;
-
- public Point(int x, int y) {
- this.x = x;
- this.y = y;
- }
-
- public int getX() {
- return x;
- }
-
- public int getY() {
- return y;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Point point = (Point) o;
- return x == point.x && y == point.y;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(x, y);
- }
-
- @Override
- public String toString() {
- return "Point{" + "x=" + x + ", y=" + y + '}';
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Pointer.java b/server/src/main/java/com/genymobile/scrcpy/Pointer.java
deleted file mode 100644
index b89cc2566..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Pointer.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.genymobile.scrcpy;
-
-public class Pointer {
-
- /**
- * Pointer id as received from the client.
- */
- private final long id;
-
- /**
- * Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}.
- */
- private final int localId;
-
- private Point point;
- private float pressure;
- private boolean up;
-
- public Pointer(long id, int localId) {
- this.id = id;
- this.localId = localId;
- }
-
- public long getId() {
- return id;
- }
-
- public int getLocalId() {
- return localId;
- }
-
- public Point getPoint() {
- return point;
- }
-
- public void setPoint(Point point) {
- this.point = point;
- }
-
- public float getPressure() {
- return pressure;
- }
-
- public void setPressure(float pressure) {
- this.pressure = pressure;
- }
-
- public boolean isUp() {
- return up;
- }
-
- public void setUp(boolean up) {
- this.up = up;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/PointersState.java b/server/src/main/java/com/genymobile/scrcpy/PointersState.java
deleted file mode 100644
index d8daaff21..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/PointersState.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PointersState {
-
- public static final int MAX_POINTERS = 10;
-
- private final List pointers = new ArrayList<>();
-
- private int indexOf(long id) {
- for (int i = 0; i < pointers.size(); ++i) {
- Pointer pointer = pointers.get(i);
- if (pointer.getId() == id) {
- return i;
- }
- }
- return -1;
- }
-
- private boolean isLocalIdAvailable(int localId) {
- for (int i = 0; i < pointers.size(); ++i) {
- Pointer pointer = pointers.get(i);
- if (pointer.getLocalId() == localId) {
- return false;
- }
- }
- return true;
- }
-
- private int nextUnusedLocalId() {
- for (int localId = 0; localId < MAX_POINTERS; ++localId) {
- if (isLocalIdAvailable(localId)) {
- return localId;
- }
- }
- return -1;
- }
-
- public Pointer get(int index) {
- return pointers.get(index);
- }
-
- public int getPointerIndex(long id) {
- int index = indexOf(id);
- if (index != -1) {
- // already exists, return it
- return index;
- }
- if (pointers.size() >= MAX_POINTERS) {
- // it's full
- return -1;
- }
- // id 0 is reserved for mouse events
- int localId = nextUnusedLocalId();
- if (localId == -1) {
- throw new AssertionError("pointers.size() < maxFingers implies that a local id is available");
- }
- Pointer pointer = new Pointer(id, localId);
- pointers.add(pointer);
- // return the index of the pointer
- return pointers.size() - 1;
- }
-
- /**
- * Initialize the motion event parameters.
- *
- * @param props the pointer properties
- * @param coords the pointer coordinates
- * @return The number of items initialized (the number of pointers).
- */
- public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) {
- int count = pointers.size();
- for (int i = 0; i < count; ++i) {
- Pointer pointer = pointers.get(i);
-
- // id 0 is reserved for mouse events
- props[i].id = pointer.getLocalId();
-
- Point point = pointer.getPoint();
- coords[i].x = point.getX();
- coords[i].y = point.getY();
- coords[i].pressure = pointer.getPressure();
- }
- cleanUp();
- return count;
- }
-
- /**
- * Remove all pointers which are UP.
- */
- private void cleanUp() {
- for (int i = pointers.size() - 1; i >= 0; --i) {
- Pointer pointer = pointers.get(i);
- if (pointer.isUp()) {
- pointers.remove(i);
- }
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Position.java b/server/src/main/java/com/genymobile/scrcpy/Position.java
deleted file mode 100644
index e9b6d8a27..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Position.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.genymobile.scrcpy;
-
-import java.util.Objects;
-
-public class Position {
- private Point point;
- private Size screenSize;
-
- public Position(Point point, Size screenSize) {
- this.point = point;
- this.screenSize = screenSize;
- }
-
- public Position(int x, int y, int screenWidth, int screenHeight) {
- this(new Point(x, y), new Size(screenWidth, screenHeight));
- }
-
- public Point getPoint() {
- return point;
- }
-
- public Size getScreenSize() {
- return screenSize;
- }
-
- public Position rotate(int rotation) {
- switch (rotation) {
- case 1:
- return new Position(new Point(screenSize.getHeight() - point.getY(), point.getX()), screenSize.rotate());
- case 2:
- return new Position(new Point(screenSize.getWidth() - point.getX(), screenSize.getHeight() - point.getY()), screenSize);
- case 3:
- return new Position(new Point(point.getY(), screenSize.getWidth() - point.getX()), screenSize.rotate());
- default:
- return this;
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Position position = (Position) o;
- return Objects.equals(point, position.point) && Objects.equals(screenSize, position.screenSize);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(point, screenSize);
- }
-
- @Override
- public String toString() {
- return "Position{" + "point=" + point + ", screenSize=" + screenSize + '}';
- }
-
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
deleted file mode 100644
index e95896d37..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
+++ /dev/null
@@ -1,304 +0,0 @@
-package com.genymobile.scrcpy;
-
-import com.genymobile.scrcpy.wrappers.SurfaceControl;
-
-import android.graphics.Rect;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.os.IBinder;
-import android.view.Surface;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class ScreenEncoder implements Device.RotationListener {
-
- private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
- private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
- private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
-
- // Keep the values in descending order
- private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
-
- private static final long PACKET_FLAG_CONFIG = 1L << 63;
- private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
-
- private final AtomicBoolean rotationChanged = new AtomicBoolean();
- private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
-
- private final String encoderName;
- private final List codecOptions;
- private final int bitRate;
- private final int maxFps;
- private final boolean sendFrameMeta;
- private final boolean downsizeOnError;
- private long ptsOrigin;
-
- private boolean firstFrameSent;
-
- public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName,
- boolean downsizeOnError) {
- this.sendFrameMeta = sendFrameMeta;
- this.bitRate = bitRate;
- this.maxFps = maxFps;
- this.codecOptions = codecOptions;
- this.encoderName = encoderName;
- this.downsizeOnError = downsizeOnError;
- }
-
- @Override
- public void onRotationChanged(int rotation) {
- rotationChanged.set(true);
- }
-
- public boolean consumeRotationChange() {
- return rotationChanged.getAndSet(false);
- }
-
- public void streamScreen(Device device, FileDescriptor fd) throws IOException {
- Workarounds.prepareMainLooper();
- if (Build.BRAND.equalsIgnoreCase("meizu")) {
- //
- //
- Workarounds.fillAppInfo();
- }
-
- internalStreamScreen(device, fd);
- }
-
- private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException {
- MediaFormat format = createFormat(bitRate, maxFps, codecOptions);
- device.setRotationListener(this);
- boolean alive;
- try {
- do {
- MediaCodec codec = createCodec(encoderName);
- IBinder display = createDisplay();
- ScreenInfo screenInfo = device.getScreenInfo();
- Rect contentRect = screenInfo.getContentRect();
- // include the locked video orientation
- Rect videoRect = screenInfo.getVideoSize().toRect();
- // does not include the locked video orientation
- Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
- int videoRotation = screenInfo.getVideoRotation();
- int layerStack = device.getLayerStack();
- setSize(format, videoRect.width(), videoRect.height());
-
- Surface surface = null;
- try {
- configure(codec, format);
- surface = codec.createInputSurface();
- setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
- codec.start();
-
- alive = encode(codec, fd);
- // do not call stop() on exception, it would trigger an IllegalStateException
- codec.stop();
- } catch (IllegalStateException | IllegalArgumentException e) {
- Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
- if (!downsizeOnError || firstFrameSent) {
- // Fail immediately
- throw e;
- }
-
- int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
- if (newMaxSize == 0) {
- // Definitively fail
- throw e;
- }
-
- // Retry with a smaller device size
- Ln.i("Retrying with -m" + newMaxSize + "...");
- device.setMaxSize(newMaxSize);
- alive = true;
- } finally {
- destroyDisplay(display);
- codec.release();
- if (surface != null) {
- surface.release();
- }
- }
- } while (alive);
- } finally {
- device.setRotationListener(null);
- }
- }
-
- private static int chooseMaxSizeFallback(Size failedSize) {
- int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight());
- for (int value : MAX_SIZE_FALLBACK) {
- if (value < currentMaxSize) {
- // We found a smaller value to reduce the video size
- return value;
- }
- }
- // No fallback, fail definitively
- return 0;
- }
-
- private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
- boolean eof = false;
- MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-
- while (!consumeRotationChange() && !eof) {
- int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
- eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
- try {
- if (consumeRotationChange()) {
- // must restart encoding with new size
- break;
- }
- if (outputBufferId >= 0) {
- ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
-
- if (sendFrameMeta) {
- writeFrameMeta(fd, bufferInfo, codecBuffer.remaining());
- }
-
- IO.writeFully(fd, codecBuffer);
- if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
- // If this is not a config packet, then it contains a frame
- firstFrameSent = true;
- }
- }
- } finally {
- if (outputBufferId >= 0) {
- codec.releaseOutputBuffer(outputBufferId, false);
- }
- }
- }
-
- return !eof;
- }
-
- private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, int packetSize) throws IOException {
- headerBuffer.clear();
-
- long pts;
- if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
- pts = PACKET_FLAG_CONFIG; // non-media data packet
- } else {
- if (ptsOrigin == 0) {
- ptsOrigin = bufferInfo.presentationTimeUs;
- }
- pts = bufferInfo.presentationTimeUs - ptsOrigin;
- if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
- pts |= PACKET_FLAG_KEY_FRAME;
- }
- }
-
- headerBuffer.putLong(pts);
- headerBuffer.putInt(packetSize);
- headerBuffer.flip();
- IO.writeFully(fd, headerBuffer);
- }
-
- private static MediaCodecInfo[] listEncoders() {
- List result = new ArrayList<>();
- MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- for (MediaCodecInfo codecInfo : list.getCodecInfos()) {
- if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- result.add(codecInfo);
- }
- }
- return result.toArray(new MediaCodecInfo[result.size()]);
- }
-
- private static MediaCodec createCodec(String encoderName) throws IOException {
- if (encoderName != null) {
- Ln.d("Creating encoder by name: '" + encoderName + "'");
- try {
- return MediaCodec.createByCodecName(encoderName);
- } catch (IllegalArgumentException e) {
- MediaCodecInfo[] encoders = listEncoders();
- throw new InvalidEncoderException(encoderName, encoders);
- }
- }
- MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
- Ln.d("Using encoder: '" + codec.getName() + "'");
- return codec;
- }
-
- private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
- String key = codecOption.getKey();
- Object value = codecOption.getValue();
-
- if (value instanceof Integer) {
- format.setInteger(key, (Integer) value);
- } else if (value instanceof Long) {
- format.setLong(key, (Long) value);
- } else if (value instanceof Float) {
- format.setFloat(key, (Float) value);
- } else if (value instanceof String) {
- format.setString(key, (String) value);
- }
-
- Ln.d("Codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value);
- }
-
- private static MediaFormat createFormat(int bitRate, int maxFps, List codecOptions) {
- MediaFormat format = new MediaFormat();
- format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
- format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
- // must be present to configure the encoder, but does not impact the actual frame rate, which is variable
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
- format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
- // display the very first frame, and recover from bad quality when no new frames
- format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
- if (maxFps > 0) {
- // The key existed privately before Android 10:
- //
- //
- format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps);
- }
-
- if (codecOptions != null) {
- for (CodecOption option : codecOptions) {
- setCodecOption(format, option);
- }
- }
-
- return format;
- }
-
- private static IBinder createDisplay() {
- // Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
- // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
- boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S"
- .equals(Build.VERSION.CODENAME));
- return SurfaceControl.createDisplay("scrcpy", secure);
- }
-
- private static void configure(MediaCodec codec, MediaFormat format) {
- codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
- }
-
- private static void setSize(MediaFormat format, int width, int height) {
- format.setInteger(MediaFormat.KEY_WIDTH, width);
- format.setInteger(MediaFormat.KEY_HEIGHT, height);
- }
-
- private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) {
- SurfaceControl.openTransaction();
- try {
- SurfaceControl.setDisplaySurface(display, surface);
- SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect);
- SurfaceControl.setDisplayLayerStack(display, layerStack);
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
- private static void destroyDisplay(IBinder display) {
- SurfaceControl.destroyDisplay(display);
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java
deleted file mode 100644
index 8e5b401f1..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java
+++ /dev/null
@@ -1,166 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.graphics.Rect;
-
-public final class ScreenInfo {
- /**
- * Device (physical) size, possibly cropped
- */
- private final Rect contentRect; // device size, possibly cropped
-
- /**
- * Video size, possibly smaller than the device size, already taking the device rotation and crop into account.
- *
- * However, it does not include the locked video orientation.
- */
- private final Size unlockedVideoSize;
-
- /**
- * Device rotation, related to the natural device orientation (0, 1, 2 or 3)
- */
- private final int deviceRotation;
-
- /**
- * The locked video orientation (-1: disabled, 0: normal, 1: 90° CCW, 2: 180°, 3: 90° CW)
- */
- private final int lockedVideoOrientation;
-
- public ScreenInfo(Rect contentRect, Size unlockedVideoSize, int deviceRotation, int lockedVideoOrientation) {
- this.contentRect = contentRect;
- this.unlockedVideoSize = unlockedVideoSize;
- this.deviceRotation = deviceRotation;
- this.lockedVideoOrientation = lockedVideoOrientation;
- }
-
- public Rect getContentRect() {
- return contentRect;
- }
-
- /**
- * Return the video size as if locked video orientation was not set.
- *
- * @return the unlocked video size
- */
- public Size getUnlockedVideoSize() {
- return unlockedVideoSize;
- }
-
- /**
- * Return the actual video size if locked video orientation is set.
- *
- * @return the actual video size
- */
- public Size getVideoSize() {
- if (getVideoRotation() % 2 == 0) {
- return unlockedVideoSize;
- }
-
- return unlockedVideoSize.rotate();
- }
-
- public int getDeviceRotation() {
- return deviceRotation;
- }
-
- public ScreenInfo withDeviceRotation(int newDeviceRotation) {
- if (newDeviceRotation == deviceRotation) {
- return this;
- }
- // true if changed between portrait and landscape
- boolean orientationChanged = (deviceRotation + newDeviceRotation) % 2 != 0;
- Rect newContentRect;
- Size newUnlockedVideoSize;
- if (orientationChanged) {
- newContentRect = flipRect(contentRect);
- newUnlockedVideoSize = unlockedVideoSize.rotate();
- } else {
- newContentRect = contentRect;
- newUnlockedVideoSize = unlockedVideoSize;
- }
- return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
- }
-
- public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) {
- if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
- // The user requested to lock the video orientation to the current orientation
- lockedVideoOrientation = rotation;
- }
-
- Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
- if (crop != null) {
- if (rotation % 2 != 0) { // 180s preserve dimensions
- // the crop (provided by the user) is expressed in the natural orientation
- crop = flipRect(crop);
- }
- if (!contentRect.intersect(crop)) {
- // intersect() changes contentRect so that it is intersected with crop
- Ln.w("Crop rectangle (" + formatCrop(crop) + ") does not intersect device screen (" + formatCrop(deviceSize.toRect()) + ")");
- contentRect = new Rect(); // empty
- }
- }
-
- Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
- return new ScreenInfo(contentRect, videoSize, rotation, lockedVideoOrientation);
- }
-
- private static String formatCrop(Rect rect) {
- return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top;
- }
-
- private static Size computeVideoSize(int w, int h, int maxSize) {
- // Compute the video size and the padding of the content inside this video.
- // Principle:
- // - scale down the great side of the screen to maxSize (if necessary);
- // - scale down the other side so that the aspect ratio is preserved;
- // - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8)
- w &= ~7; // in case it's not a multiple of 8
- h &= ~7;
- if (maxSize > 0) {
- if (BuildConfig.DEBUG && maxSize % 8 != 0) {
- throw new AssertionError("Max size must be a multiple of 8");
- }
- boolean portrait = h > w;
- int major = portrait ? h : w;
- int minor = portrait ? w : h;
- if (major > maxSize) {
- int minorExact = minor * maxSize / major;
- // +4 to round the value to the nearest multiple of 8
- minor = (minorExact + 4) & ~7;
- major = maxSize;
- }
- w = portrait ? minor : major;
- h = portrait ? major : minor;
- }
- return new Size(w, h);
- }
-
- private static Rect flipRect(Rect crop) {
- return new Rect(crop.top, crop.left, crop.bottom, crop.right);
- }
-
- /**
- * Return the rotation to apply to the device rotation to get the requested locked video orientation
- *
- * @return the rotation offset
- */
- public int getVideoRotation() {
- if (lockedVideoOrientation == -1) {
- // no offset
- return 0;
- }
- return (deviceRotation + 4 - lockedVideoOrientation) % 4;
- }
-
- /**
- * Return the rotation to apply to the requested locked video orientation to get the device rotation
- *
- * @return the (reverse) rotation offset
- */
- public int getReverseVideoRotation() {
- if (lockedVideoOrientation == -1) {
- // no offset
- return 0;
- }
- return (lockedVideoOrientation + 4 - deviceRotation) % 4;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java
deleted file mode 100644
index 1df915520..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Server.java
+++ /dev/null
@@ -1,337 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.graphics.Rect;
-import android.media.MediaCodecInfo;
-import android.os.BatteryManager;
-import android.os.Build;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
-public final class Server {
-
- private Server() {
- // not instantiable
- }
-
- private static void initAndCleanUp(Options options) {
- boolean mustDisableShowTouchesOnCleanUp = false;
- int restoreStayOn = -1;
- boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
- if (options.getShowTouches() || options.getStayAwake()) {
- Settings settings = Device.getSettings();
- if (options.getShowTouches()) {
- try {
- String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
- // If "show touches" was disabled, it must be disabled back on clean up
- mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
- } catch (SettingsException e) {
- Ln.e("Could not change \"show_touches\"", e);
- }
- }
-
- if (options.getStayAwake()) {
- int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
- try {
- String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
- try {
- restoreStayOn = Integer.parseInt(oldValue);
- if (restoreStayOn == stayOn) {
- // No need to restore
- restoreStayOn = -1;
- }
- } catch (NumberFormatException e) {
- restoreStayOn = 0;
- }
- } catch (SettingsException e) {
- Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
- }
- }
- }
-
- if (options.getCleanup()) {
- try {
- CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode,
- options.getPowerOffScreenOnClose());
- } catch (IOException e) {
- Ln.e("Could not configure cleanup", e);
- }
- }
- }
-
- private static void scrcpy(Options options) throws IOException {
- Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
- final Device device = new Device(options);
- List codecOptions = options.getCodecOptions();
-
- Thread initThread = startInitThread(options);
-
- boolean tunnelForward = options.isTunnelForward();
- boolean control = options.getControl();
- boolean sendDummyByte = options.getSendDummyByte();
-
- try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) {
- if (options.getSendDeviceMeta()) {
- Size videoSize = device.getScreenInfo().getVideoSize();
- connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
- }
- ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
- options.getEncoderName(), options.getDownsizeOnError());
-
- Thread controllerThread = null;
- Thread deviceMessageSenderThread = null;
- if (control) {
- final Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn());
-
- // asynchronous
- controllerThread = startController(controller);
- deviceMessageSenderThread = startDeviceMessageSender(controller.getSender());
-
- device.setClipboardListener(new Device.ClipboardListener() {
- @Override
- public void onClipboardTextChanged(String text) {
- controller.getSender().pushClipboardText(text);
- }
- });
- }
-
- try {
- // synchronous
- screenEncoder.streamScreen(device, connection.getVideoFd());
- } catch (IOException e) {
- // this is expected on close
- Ln.d("Screen streaming stopped");
- } finally {
- initThread.interrupt();
- if (controllerThread != null) {
- controllerThread.interrupt();
- }
- if (deviceMessageSenderThread != null) {
- deviceMessageSenderThread.interrupt();
- }
- }
- }
- }
-
- private static Thread startInitThread(final Options options) {
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- initAndCleanUp(options);
- }
- });
- thread.start();
- return thread;
- }
-
- private static Thread startController(final Controller controller) {
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- controller.control();
- } catch (IOException e) {
- // this is expected on close
- Ln.d("Controller stopped");
- }
- }
- });
- thread.start();
- return thread;
- }
-
- private static Thread startDeviceMessageSender(final DeviceMessageSender sender) {
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- sender.loop();
- } catch (IOException | InterruptedException e) {
- // this is expected on close
- Ln.d("Device message sender stopped");
- }
- }
- });
- thread.start();
- return thread;
- }
-
- private static Options createOptions(String... args) {
- if (args.length < 1) {
- throw new IllegalArgumentException("Missing client version");
- }
-
- String clientVersion = args[0];
- if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
- throw new IllegalArgumentException(
- "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
- }
-
- Options options = new Options();
-
- for (int i = 1; i < args.length; ++i) {
- String arg = args[i];
- int equalIndex = arg.indexOf('=');
- if (equalIndex == -1) {
- throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\"");
- }
- String key = arg.substring(0, equalIndex);
- String value = arg.substring(equalIndex + 1);
- switch (key) {
- case "log_level":
- Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
- options.setLogLevel(level);
- break;
- case "max_size":
- int maxSize = Integer.parseInt(value) & ~7; // multiple of 8
- options.setMaxSize(maxSize);
- break;
- case "bit_rate":
- int bitRate = Integer.parseInt(value);
- options.setBitRate(bitRate);
- break;
- case "max_fps":
- int maxFps = Integer.parseInt(value);
- options.setMaxFps(maxFps);
- break;
- case "lock_video_orientation":
- int lockVideoOrientation = Integer.parseInt(value);
- options.setLockVideoOrientation(lockVideoOrientation);
- break;
- case "tunnel_forward":
- boolean tunnelForward = Boolean.parseBoolean(value);
- options.setTunnelForward(tunnelForward);
- break;
- case "crop":
- Rect crop = parseCrop(value);
- options.setCrop(crop);
- break;
- case "control":
- boolean control = Boolean.parseBoolean(value);
- options.setControl(control);
- break;
- case "display_id":
- int displayId = Integer.parseInt(value);
- options.setDisplayId(displayId);
- break;
- case "show_touches":
- boolean showTouches = Boolean.parseBoolean(value);
- options.setShowTouches(showTouches);
- break;
- case "stay_awake":
- boolean stayAwake = Boolean.parseBoolean(value);
- options.setStayAwake(stayAwake);
- break;
- case "codec_options":
- List codecOptions = CodecOption.parse(value);
- options.setCodecOptions(codecOptions);
- break;
- case "encoder_name":
- if (!value.isEmpty()) {
- options.setEncoderName(value);
- }
- break;
- case "power_off_on_close":
- boolean powerOffScreenOnClose = Boolean.parseBoolean(value);
- options.setPowerOffScreenOnClose(powerOffScreenOnClose);
- break;
- case "clipboard_autosync":
- boolean clipboardAutosync = Boolean.parseBoolean(value);
- options.setClipboardAutosync(clipboardAutosync);
- break;
- case "downsize_on_error":
- boolean downsizeOnError = Boolean.parseBoolean(value);
- options.setDownsizeOnError(downsizeOnError);
- break;
- case "cleanup":
- boolean cleanup = Boolean.parseBoolean(value);
- options.setCleanup(cleanup);
- break;
- case "power_on":
- boolean powerOn = Boolean.parseBoolean(value);
- options.setPowerOn(powerOn);
- break;
- case "send_device_meta":
- boolean sendDeviceMeta = Boolean.parseBoolean(value);
- options.setSendDeviceMeta(sendDeviceMeta);
- break;
- case "send_frame_meta":
- boolean sendFrameMeta = Boolean.parseBoolean(value);
- options.setSendFrameMeta(sendFrameMeta);
- break;
- case "send_dummy_byte":
- boolean sendDummyByte = Boolean.parseBoolean(value);
- options.setSendDummyByte(sendDummyByte);
- break;
- case "raw_video_stream":
- boolean rawVideoStream = Boolean.parseBoolean(value);
- if (rawVideoStream) {
- options.setSendDeviceMeta(false);
- options.setSendFrameMeta(false);
- options.setSendDummyByte(false);
- }
- break;
- default:
- Ln.w("Unknown server option: " + key);
- break;
- }
- }
-
- return options;
- }
-
- private static Rect parseCrop(String crop) {
- if (crop.isEmpty()) {
- return null;
- }
- // input format: "width:height:x:y"
- String[] tokens = crop.split(":");
- if (tokens.length != 4) {
- throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\"");
- }
- int width = Integer.parseInt(tokens[0]);
- int height = Integer.parseInt(tokens[1]);
- int x = Integer.parseInt(tokens[2]);
- int y = Integer.parseInt(tokens[3]);
- return new Rect(x, y, x + width, y + height);
- }
-
- private static void suggestFix(Throwable e) {
- if (e instanceof InvalidDisplayIdException) {
- InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
- int[] displayIds = idie.getAvailableDisplayIds();
- if (displayIds != null && displayIds.length > 0) {
- Ln.e("Try to use one of the available display ids:");
- for (int id : displayIds) {
- Ln.e(" scrcpy --display " + id);
- }
- }
- } else if (e instanceof InvalidEncoderException) {
- InvalidEncoderException iee = (InvalidEncoderException) e;
- MediaCodecInfo[] encoders = iee.getAvailableEncoders();
- if (encoders != null && encoders.length > 0) {
- Ln.e("Try to use one of the available encoders:");
- for (MediaCodecInfo encoder : encoders) {
- Ln.e(" scrcpy --encoder '" + encoder.getName() + "'");
- }
- }
- }
- }
-
- public static void main(String... args) throws Exception {
- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- Ln.e("Exception on thread " + t, e);
- suggestFix(e);
- }
- });
-
- Options options = createOptions(args);
-
- Ln.initLogLevel(options.getLogLevel());
-
- scrcpy(options);
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Settings.java b/server/src/main/java/com/genymobile/scrcpy/Settings.java
deleted file mode 100644
index cb15ebb46..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Settings.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.genymobile.scrcpy;
-
-import com.genymobile.scrcpy.wrappers.ContentProvider;
-import com.genymobile.scrcpy.wrappers.ServiceManager;
-
-import android.os.Build;
-
-import java.io.IOException;
-
-public class Settings {
-
- public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM;
- public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
- public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
-
- private final ServiceManager serviceManager;
-
- public Settings(ServiceManager serviceManager) {
- this.serviceManager = serviceManager;
- }
-
- private static void execSettingsPut(String table, String key, String value) throws SettingsException {
- try {
- Command.exec("settings", "put", table, key, value);
- } catch (IOException | InterruptedException e) {
- throw new SettingsException("put", table, key, value, e);
- }
- }
-
- private static String execSettingsGet(String table, String key) throws SettingsException {
- try {
- return Command.execReadLine("settings", "get", table, key);
- } catch (IOException | InterruptedException e) {
- throw new SettingsException("get", table, key, null, e);
- }
- }
-
- public String getValue(String table, String key) throws SettingsException {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
- // on Android >= 12, it always fails:
- try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
- return provider.getValue(table, key);
- } catch (SettingsException e) {
- Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e);
- }
- }
-
- return execSettingsGet(table, key);
- }
-
- public void putValue(String table, String key, String value) throws SettingsException {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
- // on Android >= 12, it always fails:
- try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
- provider.putValue(table, key, value);
- } catch (SettingsException e) {
- Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e);
- }
- }
-
- execSettingsPut(table, key, value);
- }
-
- public String getAndPutValue(String table, String key, String value) throws SettingsException {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
- // on Android >= 12, it always fails:
- try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
- String oldValue = provider.getValue(table, key);
- if (!value.equals(oldValue)) {
- provider.putValue(table, key, value);
- }
- return oldValue;
- } catch (SettingsException e) {
- Ln.w("Could not get and put settings value via ContentProvider, fallback to settings process", e);
- }
- }
-
- String oldValue = getValue(table, key);
- if (!value.equals(oldValue)) {
- putValue(table, key, value);
- }
- return oldValue;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/SettingsException.java b/server/src/main/java/com/genymobile/scrcpy/SettingsException.java
deleted file mode 100644
index 36ef63ee1..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/SettingsException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.genymobile.scrcpy;
-
-public class SettingsException extends Exception {
- private static String createMessage(String method, String table, String key, String value) {
- return "Could not access settings: " + method + " " + table + " " + key + (value != null ? " " + value : "");
- }
-
- public SettingsException(String method, String table, String key, String value, Throwable cause) {
- super(createMessage(method, table, key, value), cause);
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Size.java b/server/src/main/java/com/genymobile/scrcpy/Size.java
deleted file mode 100644
index fd4b69714..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Size.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.graphics.Rect;
-
-import java.util.Objects;
-
-public final class Size {
- private final int width;
- private final int height;
-
- public Size(int width, int height) {
- this.width = width;
- this.height = height;
- }
-
- public int getWidth() {
- return width;
- }
-
- public int getHeight() {
- return height;
- }
-
- public Size rotate() {
- return new Size(height, width);
- }
-
- public Rect toRect() {
- return new Rect(0, 0, width, height);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Size size = (Size) o;
- return width == size.width && height == size.height;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(width, height);
- }
-
- @Override
- public String toString() {
- return "Size{" + "width=" + width + ", height=" + height + '}';
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/StringUtils.java b/server/src/main/java/com/genymobile/scrcpy/StringUtils.java
deleted file mode 100644
index dac05466c..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/StringUtils.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.genymobile.scrcpy;
-
-public final class StringUtils {
- private StringUtils() {
- // not instantiable
- }
-
- public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) {
- int len = utf8.length;
- if (len <= maxLength) {
- return len;
- }
- len = maxLength;
- // see UTF-8 encoding
- while ((utf8[len] & 0x80) != 0 && (utf8[len] & 0xc0) != 0xc0) {
- // the next byte is not the start of a new UTF-8 codepoint
- // so if we would cut there, the character would be truncated
- len--;
- }
- return len;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java
deleted file mode 100644
index 0f473bc1b..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.annotation.SuppressLint;
-import android.app.Application;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Looper;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
-public final class Workarounds {
- private Workarounds() {
- // not instantiable
- }
-
- @SuppressWarnings("deprecation")
- public 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()"
- //
- //
- // Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException:
- // "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue'
- // on a null object reference"
- //
- Looper.prepareMainLooper();
- }
-
- @SuppressLint("PrivateApi,DiscouragedPrivateApi")
- public static void fillAppInfo() {
- try {
- // ActivityThread activityThread = new ActivityThread();
- Class> activityThreadClass = Class.forName("android.app.ActivityThread");
- Constructor> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
- activityThreadConstructor.setAccessible(true);
- Object activityThread = activityThreadConstructor.newInstance();
-
- // ActivityThread.sCurrentActivityThread = activityThread;
- Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
- sCurrentActivityThreadField.setAccessible(true);
- sCurrentActivityThreadField.set(null, activityThread);
-
- // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
- Class> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
- Constructor> appBindDataConstructor = appBindDataClass.getDeclaredConstructor();
- appBindDataConstructor.setAccessible(true);
- Object appBindData = appBindDataConstructor.newInstance();
-
- ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.packageName = "com.genymobile.scrcpy";
-
- // appBindData.appInfo = applicationInfo;
- Field appInfoField = appBindDataClass.getDeclaredField("appInfo");
- appInfoField.setAccessible(true);
- appInfoField.set(appBindData, applicationInfo);
-
- // activityThread.mBoundApplication = appBindData;
- Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
- mBoundApplicationField.setAccessible(true);
- mBoundApplicationField.set(activityThread, appBindData);
-
- // Context ctx = activityThread.getSystemContext();
- Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext");
- Context ctx = (Context) getSystemContextMethod.invoke(activityThread);
-
- Application app = Instrumentation.newApplication(Application.class, ctx);
-
- // activityThread.mInitialApplication = app;
- Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
- mInitialApplicationField.setAccessible(true);
- mInitialApplicationField.set(activityThread, app);
- } catch (Throwable throwable) {
- // this is a workaround, so failing is not an error
- Ln.d("Could not fill app info: " + throwable.getMessage());
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java
deleted file mode 100644
index 93ed45287..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.IInterface;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public class ActivityManager {
-
- private final IInterface manager;
- private Method getContentProviderExternalMethod;
- private boolean getContentProviderExternalMethodNewVersion = true;
- private Method removeContentProviderExternalMethod;
-
- public ActivityManager(IInterface manager) {
- this.manager = manager;
- }
-
- private Method getGetContentProviderExternalMethod() throws NoSuchMethodException {
- if (getContentProviderExternalMethod == null) {
- try {
- getContentProviderExternalMethod = manager.getClass()
- .getMethod("getContentProviderExternal", String.class, int.class, IBinder.class, String.class);
- } catch (NoSuchMethodException e) {
- // old version
- getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class);
- getContentProviderExternalMethodNewVersion = false;
- }
- }
- return getContentProviderExternalMethod;
- }
-
- private Method getRemoveContentProviderExternalMethod() throws NoSuchMethodException {
- if (removeContentProviderExternalMethod == null) {
- removeContentProviderExternalMethod = manager.getClass().getMethod("removeContentProviderExternal", String.class, IBinder.class);
- }
- return removeContentProviderExternalMethod;
- }
-
- private ContentProvider getContentProviderExternal(String name, IBinder token) {
- try {
- Method method = getGetContentProviderExternalMethod();
- Object[] args;
- if (getContentProviderExternalMethodNewVersion) {
- // new version
- args = new Object[]{name, ServiceManager.USER_ID, token, null};
- } else {
- // old version
- args = new Object[]{name, ServiceManager.USER_ID, token};
- }
- // ContentProviderHolder providerHolder = getContentProviderExternal(...);
- Object providerHolder = method.invoke(manager, args);
- if (providerHolder == null) {
- return null;
- }
- // IContentProvider provider = providerHolder.provider;
- Field providerField = providerHolder.getClass().getDeclaredField("provider");
- providerField.setAccessible(true);
- Object provider = providerField.get(providerHolder);
- if (provider == null) {
- return null;
- }
- return new ContentProvider(this, provider, name, token);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | NoSuchFieldException e) {
- Ln.e("Could not invoke method", e);
- return null;
- }
- }
-
- void removeContentProviderExternal(String name, IBinder token) {
- try {
- Method method = getRemoveContentProviderExternalMethod();
- method.invoke(manager, name, token);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- }
- }
-
- public ContentProvider createSettingsProvider() {
- return getContentProviderExternal("settings", new Binder());
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java
deleted file mode 100644
index e25b6e994..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-
-import android.content.ClipData;
-import android.content.IOnPrimaryClipChangedListener;
-import android.os.Build;
-import android.os.IInterface;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public class ClipboardManager {
- private final IInterface manager;
- private Method getPrimaryClipMethod;
- private Method setPrimaryClipMethod;
- private Method addPrimaryClipChangedListener;
-
- public ClipboardManager(IInterface manager) {
- this.manager = manager;
- }
-
- private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
- if (getPrimaryClipMethod == null) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
- } else {
- getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
- }
- }
- return getPrimaryClipMethod;
- }
-
- private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
- if (setPrimaryClipMethod == null) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
- } else {
- setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
- }
- }
- return setPrimaryClipMethod;
- }
-
- private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME);
- }
- return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
- }
-
- private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData)
- throws InvocationTargetException, IllegalAccessException {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME);
- } else {
- method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
- }
- }
-
- public CharSequence getText() {
- try {
- Method method = getGetPrimaryClipMethod();
- ClipData clipData = getPrimaryClip(method, manager);
- if (clipData == null || clipData.getItemCount() == 0) {
- return null;
- }
- return clipData.getItemAt(0).getText();
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return null;
- }
- }
-
- public boolean setText(CharSequence text) {
- try {
- Method method = getSetPrimaryClipMethod();
- ClipData clipData = ClipData.newPlainText(null, text);
- setPrimaryClip(method, manager, clipData);
- return true;
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return false;
- }
- }
-
- private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener)
- throws InvocationTargetException, IllegalAccessException {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- method.invoke(manager, listener, ServiceManager.PACKAGE_NAME);
- } else {
- method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
- }
- }
-
- private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
- if (addPrimaryClipChangedListener == null) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- addPrimaryClipChangedListener = manager.getClass()
- .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
- } else {
- addPrimaryClipChangedListener = manager.getClass()
- .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
- }
- }
- return addPrimaryClipChangedListener;
- }
-
- public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
- try {
- Method method = getAddPrimaryClipChangedListener();
- addPrimaryClipChangedListener(method, manager, listener);
- return true;
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return false;
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java
deleted file mode 100644
index 47eae64d1..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-import com.genymobile.scrcpy.SettingsException;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.os.IBinder;
-
-import java.io.Closeable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public class ContentProvider implements Closeable {
-
- public static final String TABLE_SYSTEM = "system";
- public static final String TABLE_SECURE = "secure";
- public static final String TABLE_GLOBAL = "global";
-
- // See android/providerHolder/Settings.java
- private static final String CALL_METHOD_GET_SYSTEM = "GET_system";
- private static final String CALL_METHOD_GET_SECURE = "GET_secure";
- private static final String CALL_METHOD_GET_GLOBAL = "GET_global";
-
- private static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
- private static final String CALL_METHOD_PUT_SECURE = "PUT_secure";
- private static final String CALL_METHOD_PUT_GLOBAL = "PUT_global";
-
- private static final String CALL_METHOD_USER_KEY = "_user";
-
- private static final String NAME_VALUE_TABLE_VALUE = "value";
-
- private final ActivityManager manager;
- // android.content.IContentProvider
- private final Object provider;
- private final String name;
- private final IBinder token;
-
- private Method callMethod;
- private int callMethodVersion;
-
- private Object attributionSource;
-
- ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
- this.manager = manager;
- this.provider = provider;
- this.name = name;
- this.token = token;
- }
-
- @SuppressLint("PrivateApi")
- private Method getCallMethod() throws NoSuchMethodException {
- if (callMethod == null) {
- try {
- Class> attributionSourceClass = Class.forName("android.content.AttributionSource");
- callMethod = provider.getClass().getMethod("call", attributionSourceClass, String.class, String.class, String.class, Bundle.class);
- callMethodVersion = 0;
- } catch (NoSuchMethodException | ClassNotFoundException e0) {
- // old versions
- try {
- callMethod = provider.getClass()
- .getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
- callMethodVersion = 1;
- } catch (NoSuchMethodException e1) {
- try {
- callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
- callMethodVersion = 2;
- } catch (NoSuchMethodException e2) {
- callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
- callMethodVersion = 3;
- }
- }
- }
- }
- return callMethod;
- }
-
- @SuppressLint("PrivateApi")
- private Object getAttributionSource()
- throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- if (attributionSource == null) {
- Class> cl = Class.forName("android.content.AttributionSource$Builder");
- Object builder = cl.getConstructor(int.class).newInstance(ServiceManager.USER_ID);
- cl.getDeclaredMethod("setPackageName", String.class).invoke(builder, ServiceManager.PACKAGE_NAME);
- attributionSource = cl.getDeclaredMethod("build").invoke(builder);
- }
-
- return attributionSource;
- }
-
- private Bundle call(String callMethod, String arg, Bundle extras)
- throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
- try {
- Method method = getCallMethod();
- Object[] args;
- switch (callMethodVersion) {
- case 0:
- args = new Object[]{getAttributionSource(), "settings", callMethod, arg, extras};
- break;
- case 1:
- args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
- break;
- case 2:
- args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
- break;
- default:
- args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
- break;
- }
- return (Bundle) method.invoke(provider, args);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) {
- Ln.e("Could not invoke method", e);
- throw e;
- }
- }
-
- public void close() {
- manager.removeContentProviderExternal(name, token);
- }
-
- private static String getGetMethod(String table) {
- switch (table) {
- case TABLE_SECURE:
- return CALL_METHOD_GET_SECURE;
- case TABLE_SYSTEM:
- return CALL_METHOD_GET_SYSTEM;
- case TABLE_GLOBAL:
- return CALL_METHOD_GET_GLOBAL;
- default:
- throw new IllegalArgumentException("Invalid table: " + table);
- }
- }
-
- private static String getPutMethod(String table) {
- switch (table) {
- case TABLE_SECURE:
- return CALL_METHOD_PUT_SECURE;
- case TABLE_SYSTEM:
- return CALL_METHOD_PUT_SYSTEM;
- case TABLE_GLOBAL:
- return CALL_METHOD_PUT_GLOBAL;
- default:
- throw new IllegalArgumentException("Invalid table: " + table);
- }
- }
-
- public String getValue(String table, String key) throws SettingsException {
- String method = getGetMethod(table);
- Bundle arg = new Bundle();
- arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
- try {
- Bundle bundle = call(method, key, arg);
- if (bundle == null) {
- return null;
- }
- return bundle.getString("value");
- } catch (Exception e) {
- throw new SettingsException(table, "get", key, null, e);
- }
-
- }
-
- public void putValue(String table, String key, String value) throws SettingsException {
- String method = getPutMethod(table);
- Bundle arg = new Bundle();
- arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
- arg.putString(NAME_VALUE_TABLE_VALUE, value);
- try {
- call(method, key, arg);
- } catch (Exception e) {
- throw new SettingsException(table, "put", key, value, e);
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java
deleted file mode 100644
index cedb3f47e..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.DisplayInfo;
-import com.genymobile.scrcpy.Size;
-
-import android.os.IInterface;
-
-public final class DisplayManager {
- private final IInterface manager;
-
- public DisplayManager(IInterface manager) {
- this.manager = manager;
- }
-
- public DisplayInfo getDisplayInfo(int displayId) {
- try {
- Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId);
- if (displayInfo == null) {
- return null;
- }
- Class> cls = displayInfo.getClass();
- // width and height already take the rotation into account
- int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo);
- int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo);
- int rotation = cls.getDeclaredField("rotation").getInt(displayInfo);
- int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo);
- int flags = cls.getDeclaredField("flags").getInt(displayInfo);
- return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- public int[] getDisplayIds() {
- try {
- return (int[]) manager.getClass().getMethod("getDisplayIds").invoke(manager);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java
deleted file mode 100644
index 38e96d455..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-
-import android.view.InputEvent;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public final class InputManager {
-
- public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0;
- public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
- public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
-
- private final android.hardware.input.InputManager manager;
- private Method injectInputEventMethod;
-
- private static Method setDisplayIdMethod;
-
- public InputManager(android.hardware.input.InputManager manager) {
- this.manager = manager;
- }
-
- private Method getInjectInputEventMethod() throws NoSuchMethodException {
- if (injectInputEventMethod == null) {
- injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
- }
- return injectInputEventMethod;
- }
-
- public boolean injectInputEvent(InputEvent inputEvent, int mode) {
- try {
- Method method = getInjectInputEventMethod();
- return (boolean) method.invoke(manager, inputEvent, mode);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return false;
- }
- }
-
- private static Method getSetDisplayIdMethod() throws NoSuchMethodException {
- if (setDisplayIdMethod == null) {
- setDisplayIdMethod = InputEvent.class.getMethod("setDisplayId", int.class);
- }
- return setDisplayIdMethod;
- }
-
- public static boolean setDisplayId(InputEvent inputEvent, int displayId) {
- try {
- Method method = getSetDisplayIdMethod();
- method.invoke(inputEvent, displayId);
- return true;
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Cannot associate a display id to the input event", e);
- return false;
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java
deleted file mode 100644
index 8ff074b3d..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.os.IInterface;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public final class PowerManager {
- private final IInterface manager;
- private Method isScreenOnMethod;
-
- public PowerManager(IInterface manager) {
- this.manager = manager;
- }
-
- private Method getIsScreenOnMethod() throws NoSuchMethodException {
- if (isScreenOnMethod == null) {
- @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
- String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
- isScreenOnMethod = manager.getClass().getMethod(methodName);
- }
- return isScreenOnMethod;
- }
-
- public boolean isScreenOn() {
- try {
- Method method = getIsScreenOnMethod();
- return (boolean) method.invoke(manager);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return false;
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java
deleted file mode 100644
index ea2a07847..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import android.annotation.SuppressLint;
-import android.os.IBinder;
-import android.os.IInterface;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-@SuppressLint("PrivateApi,DiscouragedPrivateApi")
-public final class ServiceManager {
-
- public static final String PACKAGE_NAME = "com.android.shell";
- public static final int USER_ID = 0;
-
- private final Method getServiceMethod;
-
- private WindowManager windowManager;
- private DisplayManager displayManager;
- private InputManager inputManager;
- private PowerManager powerManager;
- private StatusBarManager statusBarManager;
- private ClipboardManager clipboardManager;
- private ActivityManager activityManager;
-
- public ServiceManager() {
- try {
- getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- private IInterface getService(String service, String type) {
- try {
- IBinder binder = (IBinder) getServiceMethod.invoke(null, service);
- Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class);
- return (IInterface) asInterfaceMethod.invoke(null, binder);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- public WindowManager getWindowManager() {
- if (windowManager == null) {
- windowManager = new WindowManager(getService("window", "android.view.IWindowManager"));
- }
- return windowManager;
- }
-
- public DisplayManager getDisplayManager() {
- if (displayManager == null) {
- displayManager = new DisplayManager(getService("display", "android.hardware.display.IDisplayManager"));
- }
- return displayManager;
- }
-
- public InputManager getInputManager() {
- if (inputManager == null) {
- try {
- Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
- android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
- inputManager = new InputManager(im);
- } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
- throw new AssertionError(e);
- }
- }
- return inputManager;
- }
-
- public PowerManager getPowerManager() {
- if (powerManager == null) {
- powerManager = new PowerManager(getService("power", "android.os.IPowerManager"));
- }
- return powerManager;
- }
-
- public StatusBarManager getStatusBarManager() {
- if (statusBarManager == null) {
- statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
- }
- return statusBarManager;
- }
-
- public ClipboardManager getClipboardManager() {
- if (clipboardManager == null) {
- IInterface clipboard = getService("clipboard", "android.content.IClipboard");
- if (clipboard == null) {
- // Some devices have no clipboard manager
- //
- //
- return null;
- }
- clipboardManager = new ClipboardManager(clipboard);
- }
- return clipboardManager;
- }
-
- public ActivityManager getActivityManager() {
- if (activityManager == null) {
- try {
- // On old Android versions, the ActivityManager is not exposed via AIDL,
- // so use ActivityManagerNative.getDefault()
- Class> cls = Class.forName("android.app.ActivityManagerNative");
- Method getDefaultMethod = cls.getDeclaredMethod("getDefault");
- IInterface am = (IInterface) getDefaultMethod.invoke(null);
- activityManager = new ActivityManager(am);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- return activityManager;
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java
deleted file mode 100644
index 7a19e6e5a..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-
-import android.os.IInterface;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public class StatusBarManager {
-
- private final IInterface manager;
- private Method expandNotificationsPanelMethod;
- private boolean expandNotificationPanelMethodCustomVersion;
- private Method expandSettingsPanelMethod;
- private boolean expandSettingsPanelMethodNewVersion = true;
- private Method collapsePanelsMethod;
-
- public StatusBarManager(IInterface manager) {
- this.manager = manager;
- }
-
- private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException {
- if (expandNotificationsPanelMethod == null) {
- try {
- expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
- } catch (NoSuchMethodException e) {
- // Custom version for custom vendor ROM:
- expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel", int.class);
- expandNotificationPanelMethodCustomVersion = true;
- }
- }
- return expandNotificationsPanelMethod;
- }
-
- private Method getExpandSettingsPanel() throws NoSuchMethodException {
- if (expandSettingsPanelMethod == null) {
- try {
- // Since Android 7: https://android.googlesource.com/platform/frameworks/base.git/+/a9927325eda025504d59bb6594fee8e240d95b01%5E%21/
- expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel", String.class);
- } catch (NoSuchMethodException e) {
- // old version
- expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel");
- expandSettingsPanelMethodNewVersion = false;
- }
- }
- return expandSettingsPanelMethod;
- }
-
- private Method getCollapsePanelsMethod() throws NoSuchMethodException {
- if (collapsePanelsMethod == null) {
- collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
- }
- return collapsePanelsMethod;
- }
-
- public void expandNotificationsPanel() {
- try {
- Method method = getExpandNotificationsPanelMethod();
- if (expandNotificationPanelMethodCustomVersion) {
- method.invoke(manager, 0);
- } else {
- method.invoke(manager);
- }
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- }
- }
-
- public void expandSettingsPanel() {
- try {
- Method method = getExpandSettingsPanel();
- if (expandSettingsPanelMethodNewVersion) {
- // new version
- method.invoke(manager, (Object) null);
- } else {
- // old version
- method.invoke(manager);
- }
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- }
- }
-
- public void collapsePanels() {
- try {
- Method method = getCollapsePanelsMethod();
- method.invoke(manager);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java
deleted file mode 100644
index 8fbb860b9..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-
-import android.annotation.SuppressLint;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.IBinder;
-import android.view.Surface;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-@SuppressLint("PrivateApi")
-public final class SurfaceControl {
-
- private static final Class> CLASS;
-
- // see
- public static final int POWER_MODE_OFF = 0;
- public static final int POWER_MODE_NORMAL = 2;
-
- static {
- try {
- CLASS = Class.forName("android.view.SurfaceControl");
- } catch (ClassNotFoundException e) {
- throw new AssertionError(e);
- }
- }
-
- private static Method getBuiltInDisplayMethod;
- private static Method setDisplayPowerModeMethod;
-
- private SurfaceControl() {
- // only static methods
- }
-
- public static void openTransaction() {
- try {
- CLASS.getMethod("openTransaction").invoke(null);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- public static void closeTransaction() {
- try {
- CLASS.getMethod("closeTransaction").invoke(null);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- public static void setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) {
- try {
- CLASS.getMethod("setDisplayProjection", IBinder.class, int.class, Rect.class, Rect.class)
- .invoke(null, displayToken, orientation, layerStackRect, displayRect);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- try {
- CLASS.getMethod("setDisplayLayerStack", IBinder.class, int.class).invoke(null, displayToken, layerStack);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- try {
- CLASS.getMethod("setDisplaySurface", IBinder.class, Surface.class).invoke(null, displayToken, surface);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- public static IBinder createDisplay(String name, boolean secure) {
- try {
- return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-
- private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {
- if (getBuiltInDisplayMethod == null) {
- // the method signature has changed in Android Q
- //
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
- } else {
- getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
- }
- }
- return getBuiltInDisplayMethod;
- }
-
- public static IBinder getBuiltInDisplay() {
-
- try {
- Method method = getGetBuiltInDisplayMethod();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- // call getBuiltInDisplay(0)
- return (IBinder) method.invoke(null, 0);
- }
-
- // call getInternalDisplayToken()
- return (IBinder) method.invoke(null);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return null;
- }
- }
-
- private static Method getSetDisplayPowerModeMethod() throws NoSuchMethodException {
- if (setDisplayPowerModeMethod == null) {
- setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
- }
- return setDisplayPowerModeMethod;
- }
-
- public static boolean setDisplayPowerMode(IBinder displayToken, int mode) {
- try {
- Method method = getSetDisplayPowerModeMethod();
- method.invoke(null, displayToken, mode);
- return true;
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return false;
- }
- }
-
- public static void destroyDisplay(IBinder displayToken) {
- try {
- CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-}
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java
deleted file mode 100644
index faa366a5d..000000000
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package com.genymobile.scrcpy.wrappers;
-
-import com.genymobile.scrcpy.Ln;
-
-import android.os.IInterface;
-import android.view.IRotationWatcher;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public final class WindowManager {
- private final IInterface manager;
- private Method getRotationMethod;
- private Method freezeRotationMethod;
- private Method isRotationFrozenMethod;
- private Method thawRotationMethod;
-
- public WindowManager(IInterface manager) {
- this.manager = manager;
- }
-
- private Method getGetRotationMethod() throws NoSuchMethodException {
- if (getRotationMethod == null) {
- Class> cls = manager.getClass();
- try {
- // method changed since this commit:
- // https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2
- getRotationMethod = cls.getMethod("getDefaultDisplayRotation");
- } catch (NoSuchMethodException e) {
- // old version
- getRotationMethod = cls.getMethod("getRotation");
- }
- }
- return getRotationMethod;
- }
-
- private Method getFreezeRotationMethod() throws NoSuchMethodException {
- if (freezeRotationMethod == null) {
- freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
- }
- return freezeRotationMethod;
- }
-
- private Method getIsRotationFrozenMethod() throws NoSuchMethodException {
- if (isRotationFrozenMethod == null) {
- isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
- }
- return isRotationFrozenMethod;
- }
-
- private Method getThawRotationMethod() throws NoSuchMethodException {
- if (thawRotationMethod == null) {
- thawRotationMethod = manager.getClass().getMethod("thawRotation");
- }
- return thawRotationMethod;
- }
-
- public int getRotation() {
- try {
- Method method = getGetRotationMethod();
- return (int) method.invoke(manager);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return 0;
- }
- }
-
- public void freezeRotation(int rotation) {
- try {
- Method method = getFreezeRotationMethod();
- method.invoke(manager, rotation);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- }
- }
-
- public boolean isRotationFrozen() {
- try {
- Method method = getIsRotationFrozenMethod();
- return (boolean) method.invoke(manager);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- return false;
- }
- }
-
- public void thawRotation() {
- try {
- Method method = getThawRotationMethod();
- method.invoke(manager);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
- Ln.e("Could not invoke method", e);
- }
- }
-
- public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
- try {
- Class> cls = manager.getClass();
- try {
- // display parameter added since this commit:
- // https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
- cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
- } catch (NoSuchMethodException e) {
- // old version
- cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
- }
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
-}
diff --git a/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java b/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java
deleted file mode 100644
index ad8022587..000000000
--- a/server/src/test/java/com/genymobile/scrcpy/CodecOptionsTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.genymobile.scrcpy;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.List;
-
-public class CodecOptionsTest {
-
- @Test
- public void testIntegerImplicit() {
- List codecOptions = CodecOption.parse("some_key=5");
-
- Assert.assertEquals(1, codecOptions.size());
-
- CodecOption option = codecOptions.get(0);
- Assert.assertEquals("some_key", option.getKey());
- Assert.assertEquals(5, option.getValue());
- }
-
- @Test
- public void testInteger() {
- List codecOptions = CodecOption.parse("some_key:int=5");
-
- Assert.assertEquals(1, codecOptions.size());
-
- CodecOption option = codecOptions.get(0);
- Assert.assertEquals("some_key", option.getKey());
- Assert.assertTrue(option.getValue() instanceof Integer);
- Assert.assertEquals(5, option.getValue());
- }
-
- @Test
- public void testLong() {
- List codecOptions = CodecOption.parse("some_key:long=5");
-
- Assert.assertEquals(1, codecOptions.size());
-
- CodecOption option = codecOptions.get(0);
- Assert.assertEquals("some_key", option.getKey());
- Assert.assertTrue(option.getValue() instanceof Long);
- Assert.assertEquals(5L, option.getValue());
- }
-
- @Test
- public void testFloat() {
- List codecOptions = CodecOption.parse("some_key:float=4.5");
-
- Assert.assertEquals(1, codecOptions.size());
-
- CodecOption option = codecOptions.get(0);
- Assert.assertEquals("some_key", option.getKey());
- Assert.assertTrue(option.getValue() instanceof Float);
- Assert.assertEquals(4.5f, option.getValue());
- }
-
- @Test
- public void testString() {
- List codecOptions = CodecOption.parse("some_key:string=some_value");
-
- Assert.assertEquals(1, codecOptions.size());
-
- CodecOption option = codecOptions.get(0);
- Assert.assertEquals("some_key", option.getKey());
- Assert.assertTrue(option.getValue() instanceof String);
- Assert.assertEquals("some_value", option.getValue());
- }
-
- @Test
- public void testStringEscaped() {
- List codecOptions = CodecOption.parse("some_key:string=warning\\,this_is_not=a_new_key");
-
- Assert.assertEquals(1, codecOptions.size());
-
- CodecOption option = codecOptions.get(0);
- Assert.assertEquals("some_key", option.getKey());
- Assert.assertTrue(option.getValue() instanceof String);
- Assert.assertEquals("warning,this_is_not=a_new_key", option.getValue());
- }
-
- @Test
- public void testList() {
- List codecOptions = CodecOption.parse("a=1,b:int=2,c:long=3,d:float=4.5,e:string=a\\,b=c");
-
- Assert.assertEquals(5, codecOptions.size());
-
- CodecOption option;
-
- option = codecOptions.get(0);
- Assert.assertEquals("a", option.getKey());
- Assert.assertTrue(option.getValue() instanceof Integer);
- Assert.assertEquals(1, option.getValue());
-
- option = codecOptions.get(1);
- Assert.assertEquals("b", option.getKey());
- Assert.assertTrue(option.getValue() instanceof Integer);
- Assert.assertEquals(2, option.getValue());
-
- option = codecOptions.get(2);
- Assert.assertEquals("c", option.getKey());
- Assert.assertTrue(option.getValue() instanceof Long);
- Assert.assertEquals(3L, option.getValue());
-
- option = codecOptions.get(3);
- Assert.assertEquals("d", option.getKey());
- Assert.assertTrue(option.getValue() instanceof Float);
- Assert.assertEquals(4.5f, option.getValue());
-
- option = codecOptions.get(4);
- Assert.assertEquals("e", option.getKey());
- Assert.assertTrue(option.getValue() instanceof String);
- Assert.assertEquals("a,b=c", option.getValue());
- }
-}
diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java
deleted file mode 100644
index 2a4ffe752..000000000
--- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java
+++ /dev/null
@@ -1,405 +0,0 @@
-package com.genymobile.scrcpy;
-
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-
-public class ControlMessageReaderTest {
-
- @Test
- public void testParseKeycodeEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
- dos.writeByte(KeyEvent.ACTION_UP);
- dos.writeInt(KeyEvent.KEYCODE_ENTER);
- dos.writeInt(5); // repeat
- dos.writeInt(KeyEvent.META_CTRL_ON);
- byte[] packet = bos.toByteArray();
-
- // The message type (1 byte) does not count
- Assert.assertEquals(ControlMessageReader.INJECT_KEYCODE_PAYLOAD_LENGTH, packet.length - 1);
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
- Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
- Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
- Assert.assertEquals(5, event.getRepeat());
- Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
- }
-
- @Test
- public void testParseTextEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
- byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
- dos.writeInt(text.length);
- dos.write(text);
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
- Assert.assertEquals("testé", event.getText());
- }
-
- @Test
- public void testParseLongTextEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
- byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
- Arrays.fill(text, (byte) 'a');
- dos.writeInt(text.length);
- dos.write(text);
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_INJECT_TEXT, event.getType());
- Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
- }
-
- @Test
- public void testParseTouchEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_INJECT_TOUCH_EVENT);
- dos.writeByte(MotionEvent.ACTION_DOWN);
- dos.writeLong(-42); // pointerId
- dos.writeInt(100);
- dos.writeInt(200);
- dos.writeShort(1080);
- dos.writeShort(1920);
- dos.writeShort(0xffff); // pressure
- dos.writeInt(MotionEvent.BUTTON_PRIMARY);
-
- byte[] packet = bos.toByteArray();
-
- // The message type (1 byte) does not count
- Assert.assertEquals(ControlMessageReader.INJECT_TOUCH_EVENT_PAYLOAD_LENGTH, packet.length - 1);
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_INJECT_TOUCH_EVENT, event.getType());
- Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
- Assert.assertEquals(-42, event.getPointerId());
- Assert.assertEquals(100, event.getPosition().getPoint().getX());
- Assert.assertEquals(200, event.getPosition().getPoint().getY());
- Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
- Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
- Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact
- Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons());
- }
-
- @Test
- public void testParseScrollEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_INJECT_SCROLL_EVENT);
- dos.writeInt(260);
- dos.writeInt(1026);
- dos.writeShort(1080);
- dos.writeShort(1920);
- dos.writeInt(1);
- dos.writeInt(-1);
- dos.writeInt(1);
-
- byte[] packet = bos.toByteArray();
-
- // The message type (1 byte) does not count
- Assert.assertEquals(ControlMessageReader.INJECT_SCROLL_EVENT_PAYLOAD_LENGTH, packet.length - 1);
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_INJECT_SCROLL_EVENT, event.getType());
- Assert.assertEquals(260, event.getPosition().getPoint().getX());
- Assert.assertEquals(1026, event.getPosition().getPoint().getY());
- Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
- Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
- Assert.assertEquals(1, event.getHScroll());
- Assert.assertEquals(-1, event.getVScroll());
- Assert.assertEquals(1, event.getButtons());
- }
-
- @Test
- public void testParseBackOrScreenOnEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
- dos.writeByte(KeyEvent.ACTION_UP);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
- Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
- }
-
- @Test
- public void testParseExpandNotificationPanelEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL, event.getType());
- }
-
- @Test
- public void testParseExpandSettingsPanelEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL, event.getType());
- }
-
- @Test
- public void testParseCollapsePanelsEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_COLLAPSE_PANELS);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_PANELS, event.getType());
- }
-
- @Test
- public void testParseGetClipboardEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
- dos.writeByte(ControlMessage.COPY_KEY_COPY);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
- Assert.assertEquals(ControlMessage.COPY_KEY_COPY, event.getCopyKey());
- }
-
- @Test
- public void testParseSetClipboardEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
- dos.writeLong(0x0102030405060708L); // sequence
- dos.writeByte(1); // paste
- byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
- dos.writeInt(text.length);
- dos.write(text);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
- Assert.assertEquals(0x0102030405060708L, event.getSequence());
- Assert.assertEquals("testé", event.getText());
- Assert.assertTrue(event.getPaste());
- }
-
- @Test
- public void testParseBigSetClipboardEvent() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
-
- byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
- dos.writeLong(0x0807060504030201L); // sequence
- dos.writeByte(1); // paste
- Arrays.fill(rawText, (byte) 'a');
- String text = new String(rawText, 0, rawText.length);
-
- dos.writeInt(rawText.length);
- dos.write(rawText);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
- Assert.assertEquals(0x0807060504030201L, event.getSequence());
- Assert.assertEquals(text, event.getText());
- Assert.assertTrue(event.getPaste());
- }
-
- @Test
- public void testParseSetScreenPowerMode() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_SET_SCREEN_POWER_MODE);
- dos.writeByte(Device.POWER_MODE_NORMAL);
-
- byte[] packet = bos.toByteArray();
-
- // The message type (1 byte) does not count
- Assert.assertEquals(ControlMessageReader.SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH, packet.length - 1);
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_SET_SCREEN_POWER_MODE, event.getType());
- Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction());
- }
-
- @Test
- public void testParseRotateDevice() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(ControlMessage.TYPE_ROTATE_DEVICE);
-
- byte[] packet = bos.toByteArray();
-
- reader.readFrom(new ByteArrayInputStream(packet));
- ControlMessage event = reader.next();
-
- Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
- }
-
- @Test
- public void testMultiEvents() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
-
- dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
- dos.writeByte(KeyEvent.ACTION_UP);
- dos.writeInt(KeyEvent.KEYCODE_ENTER);
- dos.writeInt(0); // repeat
- dos.writeInt(KeyEvent.META_CTRL_ON);
-
- dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
- dos.writeByte(MotionEvent.ACTION_DOWN);
- dos.writeInt(MotionEvent.BUTTON_PRIMARY);
- dos.writeInt(1); // repeat
- dos.writeInt(KeyEvent.META_CTRL_ON);
-
- byte[] packet = bos.toByteArray();
- reader.readFrom(new ByteArrayInputStream(packet));
-
- ControlMessage event = reader.next();
- Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
- Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
- Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
- Assert.assertEquals(0, event.getRepeat());
- Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
-
- event = reader.next();
- Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
- Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
- Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
- Assert.assertEquals(1, event.getRepeat());
- Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
- }
-
- @Test
- public void testPartialEvents() throws IOException {
- ControlMessageReader reader = new ControlMessageReader();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
-
- dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
- dos.writeByte(KeyEvent.ACTION_UP);
- dos.writeInt(KeyEvent.KEYCODE_ENTER);
- dos.writeInt(4); // repeat
- dos.writeInt(KeyEvent.META_CTRL_ON);
-
- dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
- dos.writeByte(MotionEvent.ACTION_DOWN);
-
- byte[] packet = bos.toByteArray();
- reader.readFrom(new ByteArrayInputStream(packet));
-
- ControlMessage event = reader.next();
- Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
- Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
- Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
- Assert.assertEquals(4, event.getRepeat());
- Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
-
- event = reader.next();
- Assert.assertNull(event); // the event is not complete
-
- bos.reset();
- dos.writeInt(MotionEvent.BUTTON_PRIMARY);
- dos.writeInt(5); // repeat
- dos.writeInt(KeyEvent.META_CTRL_ON);
- packet = bos.toByteArray();
- reader.readFrom(new ByteArrayInputStream(packet));
-
- // the event is now complete
- event = reader.next();
- Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
- Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
- Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
- Assert.assertEquals(5, event.getRepeat());
- Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
- }
-}
diff --git a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java
deleted file mode 100644
index 7b917d337..000000000
--- a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.genymobile.scrcpy;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-public class DeviceMessageWriterTest {
-
- @Test
- public void testSerializeClipboard() throws IOException {
- DeviceMessageWriter writer = new DeviceMessageWriter();
-
- String text = "aéûoç";
- byte[] data = text.getBytes(StandardCharsets.UTF_8);
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
- dos.writeInt(data.length);
- dos.write(data);
-
- byte[] expected = bos.toByteArray();
-
- DeviceMessage msg = DeviceMessage.createClipboard(text);
- bos = new ByteArrayOutputStream();
- writer.writeTo(msg, bos);
-
- byte[] actual = bos.toByteArray();
-
- Assert.assertArrayEquals(expected, actual);
- }
-
- @Test
- public void testSerializeAckSetClipboard() throws IOException {
- DeviceMessageWriter writer = new DeviceMessageWriter();
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(bos);
- dos.writeByte(DeviceMessage.TYPE_ACK_CLIPBOARD);
- dos.writeLong(0x0102030405060708L);
-
- byte[] expected = bos.toByteArray();
-
- DeviceMessage msg = DeviceMessage.createAckClipboard(0x0102030405060708L);
- bos = new ByteArrayOutputStream();
- writer.writeTo(msg, bos);
-
- byte[] actual = bos.toByteArray();
-
- Assert.assertArrayEquals(expected, actual);
- }
-}
diff --git a/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java b/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java
deleted file mode 100644
index 89799c5ec..000000000
--- a/server/src/test/java/com/genymobile/scrcpy/StringUtilsTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.genymobile.scrcpy;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.nio.charset.StandardCharsets;
-
-public class StringUtilsTest {
-
- @Test
- public void testUtf8Truncate() {
- String s = "aÉbÔc";
- byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
- Assert.assertEquals(7, utf8.length);
-
- int count;
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 1);
- Assert.assertEquals(1, count);
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 2);
- Assert.assertEquals(1, count); // É is 2 bytes-wide
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 3);
- Assert.assertEquals(3, count);
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 4);
- Assert.assertEquals(4, count);
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 5);
- Assert.assertEquals(4, count); // Ô is 2 bytes-wide
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 6);
- Assert.assertEquals(6, count);
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 7);
- Assert.assertEquals(7, count);
-
- count = StringUtils.getUtf8TruncationIndex(utf8, 8);
- Assert.assertEquals(7, count); // no more chars
- }
-}