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 - } -}