Compare commits

...

15 Commits

Author SHA1 Message Date
Romain Vimont
a5844e198e Add GitHub Actions release workflow
Fixes #4490 <https://github.com/Genymobile/scrcpy/issues/4490>
PR #5306 <https://github.com/Genymobile/scrcpy/pull/5306>
2024-10-27 20:31:24 +01:00
Romain Vimont
2687d20280 Rework release.mk for CI
Make it possible to build scrcpy-server and Windows binaries in
parallel from different GitHub Actions workflows, and to package
everything as a final step.

PR #5306 <https://github.com/Genymobile/scrcpy/pull/5306>
2024-10-27 20:29:13 +01:00
Romain Vimont
9c0a328498 Build server without meson in release.mk
This avoids to install meson/ninja to build scrcpy-server on the CI.

PR #5306 <https://github.com/Genymobile/scrcpy/pull/5306>
2024-10-27 20:26:38 +01:00
Romain Vimont
02ef3d57ce Split client and server tests in release.mk
This will allow to run server tests separately on the CI.

PR #5306 <https://github.com/Genymobile/scrcpy/pull/5306>
2024-10-27 20:26:26 +01:00
Romain Vimont
538a32a539 Fix .PHONY in release.mk
The prepare-deps recipe does not exist anymore. It has been split into
prepare-deps-win32 and prepare-deps-win64.

PR #5306 <https://github.com/Genymobile/scrcpy/pull/5306>
2024-10-27 20:26:13 +01:00
Romain Vimont
9578aae34e Use explicit constant for @TargetApi 2024-10-22 19:47:56 +02:00
Romain Vimont
7b3dd595b4 Remove useless version check
Scrcpy requires Android 5.0+, so there is no point testing for older
versions.

Btw, there were two mistakes:
 - the constant name in AndroidVersions should have been
   API_20_ANDROID_4_4W (Android 4.4 without 'W' is API 19)
 - the method isInteractive() was introduced in Android 5.0, not 4.4W:
   <037c33eae7%5E%21/>
2024-10-21 18:51:35 +02:00
Romain Vimont
a46150f753 Upgrade Android SDK to 35
Also adapt the call to build-tools/35.0.0/aidl, which now requires an
import path (-I. for the current directory).

Otherwise, it fails with:

    ERROR: android/view/IRotationWatcher.aidl:23.1-10: directory ./ is not found in any of the import paths:
     -

Also upgrade AGP (8.7.1) and Gradle (8.9), required for SDK 35.

<https://developer.android.com/build/releases/gradle-plugin#updating-gradle>
2024-10-20 15:26:19 +02:00
Romain Vimont
3acffaae57 Use explicit constants for Android versions
Who remembers code names? This avoids to check the mapping every time.
2024-10-20 13:15:22 +02:00
dillonfrederica
e33be3d288 Fix SDL_events.h include
All SDL includes must be prefixed by "SDL2/".

Fixed #5372 <https://github.com/Genymobile/scrcpy/issues/5372>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-10-12 15:57:24 +02:00
Romain Vimont
c15df01171 Reject non-positive camera sizes early
Throw an exception on parsing if the camera size dimensions are not both
positive.
2024-10-11 22:51:15 +02:00
Romain Vimont
09741bc805 Do not duplicate server string params
The server params were passed from the main thread to the server thread,
so a deep copy was performed in case the caller instance was destroyed.

But in practice, it only contains memory that lives until the end of the
program (command line arguments), so simply reference it. Several copies
of string fields were missing anyway.
2024-10-11 22:42:39 +02:00
Romain Vimont
afbaf59abb Cast to sc_tick type in conversion macros
With the old macros definitions, the type of the result depended on the
type of `sec`.

In particular, if sec is a 32-bit type, sec * 1000000 was likely to
overflow (even if the result was assigned to a sc_tick by the caller of
the macro).

This was the case on Windows, where the long type is a 32-bit signed
integer: the --time-limit argument, expressed in seconds, was first
parsed to a long value, then multiplied by 1000000 by the
SC_TICK_FROM_SEC() macro, causing an overflow when the value was greater
than 2147 (2^31 / 1000000).

Fixes #5355 <https://github.com/Genymobile/scrcpy/issues/5355>
2024-10-08 18:18:05 +02:00
Romain Vimont
5b10650f22 Fix time-limit early interruption
If a value for --time-limit was set, then the thread was not interrupted
on stop (the condvar was not signaled).
2024-10-08 18:12:55 +02:00
Yan
0d8014be52 Fix build error on macOS
Fix window access typo for macOS.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-10-07 16:54:15 +02:00
36 changed files with 343 additions and 194 deletions

147
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,147 @@
name: Build
on:
workflow_dispatch:
inputs:
name:
description: 'Version name (default is ref name)'
jobs:
build-scrcpy-server:
runs-on: ubuntu-latest
env:
GRADLE: gradle # use native gradle instead of ./gradlew in release.mk
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Test scrcpy-server
run: make -f release.mk test-server
- name: Build scrcpy-server
run: make -f release.mk build-server
- name: Upload scrcpy-server artifact
uses: actions/upload-artifact@v4
with:
name: scrcpy-server
path: build-server/server/scrcpy-server
test-client:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
- name: Build
run: |
meson setup d -Db_sanitize=address,undefined
- name: Test
run: |
meson test -Cd
build-win32:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
- name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt
- name: Build scrcpy win32
run: make -f release.mk build-win32
- name: Upload build-win32 artifact
uses: actions/upload-artifact@v4
with:
name: build-win32-intermediate
path: build-win32/dist/
build-win64:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
- name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt
- name: Build scrcpy win64
run: make -f release.mk build-win64
- name: Upload build-win64 artifact
uses: actions/upload-artifact@v4
with:
name: build-win64-intermediate
path: build-win64/dist/
package:
needs:
- build-scrcpy-server
- build-win32
- build-win64
runs-on: ubuntu-latest
env:
# $VERSION is used by release.mk
VERSION: ${{ github.event.inputs.name || github.ref_name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download scrcpy-server
uses: actions/download-artifact@v4
with:
name: scrcpy-server
path: build-server/server/
- name: Download build-win32
uses: actions/download-artifact@v4
with:
name: build-win32-intermediate
path: build-win32/dist/
- name: Download build-win64
uses: actions/download-artifact@v4
with:
name: build-win64-intermediate
path: build-win64/dist/
- name: Package
run: make -f release.mk package
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: scrcpy-release-${{ env.VERSION }}
path: release-${{ env.VERSION }}

View File

@@ -5,7 +5,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL_events.h>
#include <SDL2/SDL_events.h>
enum {
SC_EVENT_NEW_FRAME = SDL_USEREVENT,

View File

@@ -92,8 +92,8 @@ sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(window, &x, &y);
SDL_GetWindowSize(window, &w, &h);
SDL_GetWindowPosition(mc->window, &x, &y);
SDL_GetWindowSize(mc->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;

View File

@@ -66,56 +66,6 @@ get_server_path(void) {
return server_path;
}
static void
sc_server_params_destroy(struct sc_server_params *params) {
// The server stores a copy of the params provided by the user
free((char *) params->req_serial);
free((char *) params->crop);
free((char *) params->video_codec_options);
free((char *) params->audio_codec_options);
free((char *) params->video_encoder);
free((char *) params->audio_encoder);
free((char *) params->tcpip_dst);
free((char *) params->camera_id);
free((char *) params->camera_ar);
}
static bool
sc_server_params_copy(struct sc_server_params *dst,
const struct sc_server_params *src) {
*dst = *src;
// The params reference user-allocated memory, so we must copy them to
// handle them from another thread
#define COPY(FIELD) do { \
dst->FIELD = NULL; \
if (src->FIELD) { \
dst->FIELD = strdup(src->FIELD); \
if (!dst->FIELD) { \
goto error; \
} \
} \
} while(0)
COPY(req_serial);
COPY(crop);
COPY(video_codec_options);
COPY(audio_codec_options);
COPY(video_encoder);
COPY(audio_encoder);
COPY(tcpip_dst);
COPY(camera_id);
COPY(camera_ar);
#undef COPY
return true;
error:
sc_server_params_destroy(dst);
return false;
}
static bool
push_server(struct sc_intr *intr, const char *serial) {
char *server_path = get_server_path();
@@ -499,22 +449,18 @@ connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay,
bool
sc_server_init(struct sc_server *server, const struct sc_server_params *params,
const struct sc_server_callbacks *cbs, void *cbs_userdata) {
bool ok = sc_server_params_copy(&server->params, params);
if (!ok) {
LOG_OOM();
return false;
}
// The allocated data in params (const char *) must remain valid until the
// end of the program
server->params = *params;
ok = sc_mutex_init(&server->mutex);
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
sc_server_params_destroy(&server->params);
return false;
}
ok = sc_cond_init(&server->cond_stopped);
if (!ok) {
sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params);
return false;
}
@@ -522,7 +468,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
if (!ok) {
sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params);
return false;
}
@@ -1161,7 +1106,6 @@ sc_server_destroy(struct sc_server *server) {
free(server->serial);
free(server->device_socket_name);
sc_server_params_destroy(&server->params);
sc_intr_destroy(&server->intr);
sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex);

View File

@@ -10,14 +10,14 @@ typedef int64_t sc_tick;
#define SC_TICK_FREQ 1000000 // microsecond
// To be adapted if SC_TICK_FREQ changes
#define SC_TICK_TO_NS(tick) ((tick) * 1000)
#define SC_TICK_TO_US(tick) (tick)
#define SC_TICK_TO_MS(tick) ((tick) / 1000)
#define SC_TICK_TO_SEC(tick) ((tick) / 1000000)
#define SC_TICK_FROM_NS(ns) ((ns) / 1000)
#define SC_TICK_FROM_US(us) (us)
#define SC_TICK_FROM_MS(ms) ((ms) * 1000)
#define SC_TICK_FROM_SEC(sec) ((sec) * 1000000)
#define SC_TICK_TO_NS(tick) ((sc_tick) (tick) * 1000)
#define SC_TICK_TO_US(tick) ((sc_tick) tick)
#define SC_TICK_TO_MS(tick) ((sc_tick) (tick) / 1000)
#define SC_TICK_TO_SEC(tick) ((sc_tick) (tick) / 1000000)
#define SC_TICK_FROM_NS(ns) ((sc_tick) (ns) / 1000)
#define SC_TICK_FROM_US(us) ((sc_tick) us)
#define SC_TICK_FROM_MS(ms) ((sc_tick) (ms) * 1000)
#define SC_TICK_FROM_SEC(sec) ((sc_tick) (sec) * 1000000)
sc_tick
sc_tick_now(void);

View File

@@ -62,6 +62,7 @@ void
sc_timeout_stop(struct sc_timeout *timeout) {
sc_mutex_lock(&timeout->mutex);
timeout->stopped = true;
sc_cond_signal(&timeout->cond);
sc_mutex_unlock(&timeout->mutex);
}

View File

@@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.0'
classpath 'com.android.tools.build:gradle:8.7.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
# https://gradle.org/release-checksums/
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -9,13 +9,12 @@
# the server to the device.
.PHONY: default clean \
test \
test test-client test-server \
build-server \
prepare-deps \
prepare-deps-win32 prepare-deps-win64 \
build-win32 build-win64 \
dist-win32 dist-win64 \
zip-win32 zip-win64 \
release
package release
GRADLE ?= ./gradlew
@@ -26,7 +25,7 @@ WIN64_BUILD_DIR := build-win64
VERSION ?= $(shell git describe --tags --exclude='*install-release' --always)
DIST := dist
ZIP := zip
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
WIN64_TARGET_DIR := scrcpy-win64-$(VERSION)
WIN32_TARGET := $(WIN32_TARGET_DIR).zip
@@ -34,33 +33,28 @@ WIN64_TARGET := $(WIN64_TARGET_DIR).zip
RELEASE_DIR := release-$(VERSION)
release: clean test build-server zip-win32 zip-win64
mkdir -p "$(RELEASE_DIR)"
cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \
"$(RELEASE_DIR)/scrcpy-server-$(VERSION)"
cp "$(DIST)/$(WIN32_TARGET)" "$(RELEASE_DIR)"
cp "$(DIST)/$(WIN64_TARGET)" "$(RELEASE_DIR)"
cd "$(RELEASE_DIR)" && \
sha256sum "scrcpy-server-$(VERSION)" \
"scrcpy-win32-$(VERSION).zip" \
"scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt
@echo "Release generated in $(RELEASE_DIR)/"
release: clean test build-server build-win32 build-win64 package
clean:
$(GRADLE) clean
rm -rf "$(DIST)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \
rm -rf "$(ZIP)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \
"$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)"
test:
test-client:
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
meson setup "$(TEST_BUILD_DIR)" -Db_sanitize=address )
ninja -C "$(TEST_BUILD_DIR)"
test-server:
$(GRADLE) -p server check
test: test-client test-server
build-server:
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
ninja -C "$(SERVER_BUILD_DIR)"
$(GRADLE) -p server assembleRelease
mkdir -p "$(SERVER_BUILD_DIR)/server"
cp server/build/outputs/apk/release/server-release-unsigned.apk \
"$(SERVER_BUILD_DIR)/server/scrcpy-server"
prepare-deps-win32:
@app/deps/adb.sh win32
@@ -86,6 +80,15 @@ build-win32: prepare-deps-win32
-Dcompile_server=false \
-Dportable=true
ninja -C "$(WIN32_BUILD_DIR)"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$(WIN32_BUILD_DIR)/dist"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(WIN32_BUILD_DIR)/dist/"
cp app/data/scrcpy-console.bat "$(WIN32_BUILD_DIR)/dist/"
cp app/data/scrcpy-noconsole.vbs "$(WIN32_BUILD_DIR)/dist/"
cp app/data/icon.png "$(WIN32_BUILD_DIR)/dist/"
cp app/data/open_a_terminal_here.bat "$(WIN32_BUILD_DIR)/dist/"
cp app/deps/work/install/win32/bin/*.dll "$(WIN32_BUILD_DIR)/dist/"
cp app/deps/work/install/win32/bin/adb.exe "$(WIN32_BUILD_DIR)/dist/"
build-win64: prepare-deps-win64
rm -rf "$(WIN64_BUILD_DIR)"
@@ -99,33 +102,40 @@ build-win64: prepare-deps-win64
-Dcompile_server=false \
-Dportable=true
ninja -C "$(WIN64_BUILD_DIR)"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$(WIN64_BUILD_DIR)/dist"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(WIN64_BUILD_DIR)/dist/"
cp app/data/scrcpy-console.bat "$(WIN64_BUILD_DIR)/dist/"
cp app/data/scrcpy-noconsole.vbs "$(WIN64_BUILD_DIR)/dist/"
cp app/data/icon.png "$(WIN64_BUILD_DIR)/dist/"
cp app/data/open_a_terminal_here.bat "$(WIN64_BUILD_DIR)/dist/"
cp app/deps/work/install/win64/bin/*.dll "$(WIN64_BUILD_DIR)/dist/"
cp app/deps/work/install/win64/bin/adb.exe "$(WIN64_BUILD_DIR)/dist/"
dist-win32: build-server build-win32
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/deps/work/install/win32/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/deps/work/install/win32/bin/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/deps/work/install/win64/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/deps/work/install/win64/bin/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)"; \
zip-win32:
mkdir -p "$(ZIP)/$(WIN32_TARGET_DIR)"
cp -r "$(WIN32_BUILD_DIR)/dist/." "$(ZIP)/$(WIN32_TARGET_DIR)/"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(ZIP)/$(WIN32_TARGET_DIR)/"
cd "$(ZIP)"; \
zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
rm -rf "$(ZIP)/$(WIN32_TARGET_DIR)"
zip-win64: dist-win64
cd "$(DIST)"; \
zip-win64:
mkdir -p "$(ZIP)/$(WIN64_TARGET_DIR)"
cp -r "$(WIN64_BUILD_DIR)/dist/." "$(ZIP)/$(WIN64_TARGET_DIR)/"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(ZIP)/$(WIN64_TARGET_DIR)/"
cd "$(ZIP)"; \
zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"
rm -rf "$(ZIP)/$(WIN64_TARGET_DIR)"
package: zip-win32 zip-win64
mkdir -p "$(RELEASE_DIR)"
cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \
"$(RELEASE_DIR)/scrcpy-server-$(VERSION)"
cp "$(ZIP)/$(WIN32_TARGET)" "$(RELEASE_DIR)"
cp "$(ZIP)/$(WIN64_TARGET)" "$(RELEASE_DIR)"
cd "$(RELEASE_DIR)" && \
sha256sum "scrcpy-server-$(VERSION)" \
"scrcpy-win32-$(VERSION).zip" \
"scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt
@echo "Release generated in $(RELEASE_DIR)/"

View File

@@ -2,11 +2,11 @@ apply plugin: 'com.android.application'
android {
namespace 'com.genymobile.scrcpy'
compileSdk 34
compileSdk 35
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 34
targetSdkVersion 35
versionCode 20700
versionName "2.7"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

View File

@@ -14,8 +14,8 @@ set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.7
PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}
PLATFORM=${ANDROID_PLATFORM:-35}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
@@ -45,10 +45,10 @@ EOF
echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl"
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" \
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IRotationWatcher.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. \
android/content/IOnPrimaryClipChangedListener.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IDisplayFoldListener.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
SRC=( \
com/genymobile/scrcpy/*.java \

View File

@@ -0,0 +1,32 @@
package com.genymobile.scrcpy;
import android.os.Build;
/**
* Android version code constants, done right.
* <p/>
* <a href="https://apilevels.com/">API levels</a>
*/
public final class AndroidVersions {
private AndroidVersions() {
// not instantiable
}
public static final int API_21_ANDROID_5_0 = Build.VERSION_CODES.LOLLIPOP;
public static final int API_22_ANDROID_5_1 = Build.VERSION_CODES.LOLLIPOP_MR1;
public static final int API_23_ANDROID_6_0 = Build.VERSION_CODES.M;
public static final int API_24_ANDROID_7_0 = Build.VERSION_CODES.N;
public static final int API_25_ANDROID_7_1 = Build.VERSION_CODES.N_MR1;
public static final int API_26_ANDROID_8_0 = Build.VERSION_CODES.O;
public static final int API_27_ANDROID_8_1 = Build.VERSION_CODES.O_MR1;
public static final int API_28_ANDROID_9 = Build.VERSION_CODES.P;
public static final int API_29_ANDROID_10 = Build.VERSION_CODES.Q;
public static final int API_30_ANDROID_11 = Build.VERSION_CODES.R;
public static final int API_31_ANDROID_12 = Build.VERSION_CODES.S;
public static final int API_32_ANDROID_12L = Build.VERSION_CODES.S_V2;
public static final int API_33_ANDROID_13 = Build.VERSION_CODES.TIRAMISU;
public static final int API_34_ANDROID_14 = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
public static final int API_35_ANDROID_15 = Build.VERSION_CODES.VANILLA_ICE_CREAM;
}

View File

@@ -4,7 +4,6 @@ import android.annotation.TargetApi;
import android.content.AttributionSource;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Build;
import android.os.Process;
public final class FakeContext extends ContextWrapper {
@@ -32,7 +31,7 @@ public final class FakeContext extends ContextWrapper {
return PACKAGE_NAME;
}
@TargetApi(Build.VERSION_CODES.S)
@TargetApi(AndroidVersions.API_31_ANDROID_12)
@Override
public AttributionSource getAttributionSource() {
AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID);

View File

@@ -475,6 +475,9 @@ public class Options {
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Invalid non-positive size dimension: \"" + size + "\"");
}
return new Size(width, height);
}

View File

@@ -121,7 +121,7 @@ public final class Server {
}
private static void scrcpy(Options options) throws IOException, ConfigurationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && options.getVideoSource() == VideoSource.CAMERA) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
Ln.e("Camera mirroring is not supported before Android 12");
throw new ConfigurationException("Camera mirroring is not supported");
}

View File

@@ -52,7 +52,7 @@ public final class Workarounds {
}
public static void apply() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) {
// On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(),
// which requires a non-null ConfigurationController.
// ConfigurationController was introduced in Android 12, so do not attempt to set it on lower versions.
@@ -155,7 +155,7 @@ public final class Workarounds {
}
}
@TargetApi(Build.VERSION_CODES.R)
@TargetApi(AndroidVersions.API_30_ANDROID_11)
@SuppressLint("WrongConstant,MissingPermission")
public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) throws
AudioCaptureException {
@@ -226,7 +226,7 @@ public final class Workarounds {
int[] session = new int[]{AudioManager.AUDIO_SESSION_ID_GENERATE};
int initResult;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12) {
// private native final int native_setup(Object audiorecord_this,
// Object /*AudioAttributes*/ attributes,
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
@@ -252,7 +252,7 @@ public final class Workarounds {
Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel");
Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_34_ANDROID_14) {
// private native int native_setup(Object audiorecordThis,
// Object /*AudioAttributes*/ attributes,
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.Workarounds;
import com.genymobile.scrcpy.util.Ln;
@@ -45,11 +46,11 @@ public class AudioDirectCapture implements AudioCapture {
}
}
@TargetApi(Build.VERSION_CODES.M)
@TargetApi(AndroidVersions.API_23_ANDROID_6_0)
@SuppressLint({"WrongConstant", "MissingPermission"})
private static AudioRecord createAudioRecord(int audioSource) {
AudioRecord.Builder builder = new AudioRecord.Builder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) {
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
builder.setContext(FakeContext.get());
}
@@ -117,7 +118,7 @@ public class AudioDirectCapture implements AudioCapture {
@Override
public void checkCompatibility() throws AudioCaptureException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11) {
Ln.w("Audio disabled: it is not supported before Android 11");
throw new AudioCaptureException();
}
@@ -125,7 +126,7 @@ public class AudioDirectCapture implements AudioCapture {
@Override
public void start() throws AudioCaptureException {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT == AndroidVersions.API_30_ANDROID_11) {
startWorkaroundAndroid11();
try {
tryStartRecording(5, 100);
@@ -146,7 +147,7 @@ public class AudioDirectCapture implements AudioCapture {
}
@Override
@TargetApi(Build.VERSION_CODES.N)
@TargetApi(AndroidVersions.API_24_ANDROID_7_0)
public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) {
return reader.read(outDirectBuffer, outBufferInfo);
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Streamer;
@@ -93,7 +94,7 @@ public final class AudioEncoder implements AsyncProcessor {
return format;
}
@TargetApi(Build.VERSION_CODES.N)
@TargetApi(AndroidVersions.API_24_ANDROID_7_0)
private void inputThread(MediaCodec mediaCodec, AudioCapture capture) throws IOException, InterruptedException {
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
@@ -175,9 +176,9 @@ public final class AudioEncoder implements AsyncProcessor {
}
}
@TargetApi(Build.VERSION_CODES.M)
@TargetApi(AndroidVersions.API_23_ANDROID_6_0)
private void encode() throws IOException, ConfigurationException, AudioCaptureException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11) {
Ln.w("Audio disabled: it is not supported before Android 11");
streamer.writeDisableStream(false);
return;
@@ -314,7 +315,7 @@ public final class AudioEncoder implements AsyncProcessor {
}
private final class EncoderCallback extends MediaCodec.Callback {
@TargetApi(Build.VERSION_CODES.N)
@TargetApi(AndroidVersions.API_24_ANDROID_7_0)
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
try {

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.util.Ln;
@@ -108,7 +109,7 @@ public final class AudioPlaybackCapture implements AudioCapture {
@Override
public void checkCompatibility() throws AudioCaptureException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_33_ANDROID_13) {
Ln.w("Audio disabled: audio playback capture source not supported before Android 13");
throw new AudioCaptureException();
}
@@ -130,7 +131,7 @@ public final class AudioPlaybackCapture implements AudioCapture {
}
@Override
@TargetApi(Build.VERSION_CODES.N)
@TargetApi(AndroidVersions.API_24_ANDROID_7_0)
public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) {
return reader.read(outDirectBuffer, outBufferInfo);
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.device.Streamer;
import com.genymobile.scrcpy.util.IO;
@@ -24,7 +25,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
}
private void record() throws IOException, AudioCaptureException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11) {
Ln.w("Audio disabled: it is not supported before Android 11");
streamer.writeDisableStream(false);
return;

View File

@@ -1,12 +1,12 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import android.annotation.TargetApi;
import android.media.AudioRecord;
import android.media.AudioTimestamp;
import android.media.MediaCodec;
import android.os.Build;
import java.nio.ByteBuffer;
@@ -26,7 +26,7 @@ public class AudioRecordReader {
this.recorder = recorder;
}
@TargetApi(Build.VERSION_CODES.N)
@TargetApi(AndroidVersions.API_24_ANDROID_7_0)
public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) {
int r = recorder.read(outDirectBuffer, AudioConfig.MAX_READ_SIZE);
if (r <= 0) {

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.control;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.CleanUp;
import com.genymobile.scrcpy.device.Device;
@@ -318,7 +319,7 @@ public class Controller implements AsyncProcessor {
*
* Otherwise, Chrome does not work properly: <https://github.com/Genymobile/scrcpy/issues/3635>
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && source == InputDevice.SOURCE_MOUSE) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0 && source == InputDevice.SOURCE_MOUSE) {
if (action == MotionEvent.ACTION_DOWN) {
if (actionButton == buttons) {
// First button pressed: ACTION_DOWN
@@ -423,7 +424,7 @@ public class Controller implements AsyncProcessor {
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()) {
if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && 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);
@@ -448,7 +449,7 @@ public class Controller implements AsyncProcessor {
}
// On Android >= 7, also press the PASTE key if requested
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
if (paste && Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0 && device.supportsInputEvents()) {
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC);
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.control;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.StringUtils;
@@ -38,7 +39,7 @@ public final class UhidManager {
public UhidManager(DeviceMessageSender sender) {
this.sender = sender;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) {
HandlerThread thread = new HandlerThread("UHidManager");
thread.start();
queue = thread.getLooper().getQueue();
@@ -71,7 +72,7 @@ public final class UhidManager {
}
private void registerUhidListener(int id, FileDescriptor fd) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) {
queue.addOnFileDescriptorEventListener(fd, MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT, (fd2, events) -> {
try {
buffer.clear();
@@ -97,7 +98,7 @@ public final class UhidManager {
}
private void unregisterUhidListener(FileDescriptor fd) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) {
queue.removeOnFileDescriptorEventListener(fd);
}
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.device;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.Options;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
@@ -104,7 +105,7 @@ public final class Device {
}
}, displayId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) {
ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() {
@Override
public void onDisplayFoldChanged(int displayId, boolean folded) {
@@ -161,8 +162,8 @@ public final class Device {
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;
// main display or any display on Android >= 10
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10;
if (!supportsInputEvents) {
Ln.w("Input events are not supported for secondary displays before Android 10");
}
@@ -215,7 +216,7 @@ public final class Device {
}
public static boolean supportsInputEvents(int displayId) {
return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
return displayId == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10;
}
public boolean supportsInputEvents() {
@@ -323,10 +324,10 @@ public final class Device {
* @param mode one of the {@code POWER_MODE_*} constants
*/
public static boolean setScreenPowerMode(int mode) {
boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10;
if (applyToMultiPhysicalDisplays
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
&& Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14
&& Build.BRAND.equalsIgnoreCase("honor")
&& SurfaceControl.hasGetBuildInDisplayMethod()) {
// Workaround for Honor devices with Android 14:
@@ -338,7 +339,7 @@ public final class Device {
if (applyToMultiPhysicalDisplays) {
// On Android 14, these internal methods have been moved to DisplayControl
boolean useDisplayControl =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasGetPhysicalDisplayIdsMethod();
Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14 && !SurfaceControl.hasGetPhysicalDisplayIdsMethod();
// Change the power mode for all physical displays
long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds();

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.util;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.BuildConfig;
import android.os.Build;
@@ -31,7 +32,7 @@ public final class IO {
}
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) {
while (from.hasRemaining()) {
write(fd, from);
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.util;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;
@@ -34,7 +35,7 @@ public final class Settings {
}
public static String getValue(String table, String key) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT <= AndroidVersions.API_30_ANDROID_11) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key);
@@ -47,7 +48,7 @@ public final class Settings {
}
public static void putValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT <= AndroidVersions.API_30_ANDROID_11) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value);
@@ -60,7 +61,7 @@ public final class Settings {
}
public static String getAndPutValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT <= AndroidVersions.API_30_ANDROID_11) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key);

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.HandlerExecutor;
import com.genymobile.scrcpy.util.Ln;
@@ -20,7 +21,6 @@ import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaCodec;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Range;
@@ -118,7 +118,7 @@ public class CameraCapture extends SurfaceCapture {
return null;
}
@TargetApi(Build.VERSION_CODES.N)
@TargetApi(AndroidVersions.API_24_ANDROID_7_0)
private static Size selectSize(String cameraId, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, boolean highSpeed)
throws CameraAccessException {
if (explicitSize != null) {
@@ -242,7 +242,7 @@ public class CameraCapture extends SurfaceCapture {
}
@SuppressLint("MissingPermission")
@TargetApi(Build.VERSION_CODES.S)
@TargetApi(AndroidVersions.API_31_ANDROID_12)
private CameraDevice openCamera(String id) throws CameraAccessException, InterruptedException {
CompletableFuture<CameraDevice> future = new CompletableFuture<>();
ServiceManager.getCameraManager().openCamera(id, new CameraDevice.StateCallback() {
@@ -289,7 +289,7 @@ public class CameraCapture extends SurfaceCapture {
}
}
@TargetApi(Build.VERSION_CODES.S)
@TargetApi(AndroidVersions.API_31_ANDROID_12)
private CameraCaptureSession createCaptureSession(CameraDevice camera, Surface surface) throws CameraAccessException, InterruptedException {
CompletableFuture<CameraCaptureSession> future = new CompletableFuture<>();
OutputConfiguration outputConfig = new OutputConfiguration(surface);
@@ -328,7 +328,7 @@ public class CameraCapture extends SurfaceCapture {
return requestBuilder.build();
}
@TargetApi(Build.VERSION_CODES.S)
@TargetApi(AndroidVersions.API_31_ANDROID_12)
private void setRepeatingRequest(CameraCaptureSession session, CaptureRequest request) throws CameraAccessException, InterruptedException {
CameraCaptureSession.CaptureCallback callback = new CameraCaptureSession.CaptureCallback() {
@Override

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.Ln;
@@ -103,8 +104,8 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
private static IBinder createDisplay() throws Exception {
// 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));
boolean secure = Build.VERSION.SDK_INT < AndroidVersions.API_30_ANDROID_11 || (Build.VERSION.SDK_INT == AndroidVersions.API_30_ANDROID_11
&& !"S".equals(Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure);
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Size;
@@ -238,7 +239,7 @@ public class SurfaceEncoder implements AsyncProcessor {
// 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);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_24_ANDROID_7_0) {
format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
}
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.util.Ln;
@@ -7,7 +8,6 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
@@ -63,7 +63,7 @@ public final class ActivityManager {
return removeContentProviderExternalMethod;
}
@TargetApi(Build.VERSION_CODES.Q)
@TargetApi(AndroidVersions.API_29_ANDROID_10)
private ContentProvider getContentProviderExternal(String name, IBinder token) {
try {
Method method = getGetContentProviderExternalMethod();

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.util.Ln;
@@ -36,7 +37,7 @@ public final class ClipboardManager {
private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
if (getPrimaryClipMethod == null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
return getPrimaryClipMethod;
}
@@ -99,7 +100,7 @@ public final class ClipboardManager {
private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
if (setPrimaryClipMethod == null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
return setPrimaryClipMethod;
}
@@ -137,7 +138,7 @@ public final class ClipboardManager {
}
private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager) throws ReflectiveOperationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME);
}
@@ -161,7 +162,7 @@ public final class ClipboardManager {
}
private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData) throws ReflectiveOperationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME);
return;
}
@@ -210,7 +211,7 @@ public final class ClipboardManager {
private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener)
throws ReflectiveOperationException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
return;
}
@@ -230,7 +231,7 @@ public final class ClipboardManager {
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
if (addPrimaryClipChangedListener == null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
} else {

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.SettingsException;
@@ -51,7 +52,7 @@ public final class ContentProvider implements Closeable {
@SuppressLint("PrivateApi")
private Method getCallMethod() throws NoSuchMethodException {
if (callMethod == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) {
callMethod = provider.getClass().getMethod("call", AttributionSource.class, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 0;
} else {
@@ -79,7 +80,7 @@ public final class ContentProvider implements Closeable {
Method method = getCallMethod();
Object[] args;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && callMethodVersion == 0) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12 && callMethodVersion == 0) {
args = new Object[]{FakeContext.get().getAttributionSource(), "settings", callMethod, arg, extras};
} else {
switch (callMethodVersion) {

View File

@@ -1,16 +1,16 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.IBinder;
import java.lang.reflect.Method;
@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"})
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@TargetApi(AndroidVersions.API_34_ANDROID_14)
public final class DisplayControl {
private static final Class<?> CLASS;

View File

@@ -2,8 +2,6 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.util.Ln;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.IInterface;
import java.lang.reflect.Method;
@@ -23,9 +21,7 @@ public final class PowerManager {
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);
isScreenOnMethod = manager.getClass().getMethod("isInteractive");
}
return isScreenOnMethod;
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import android.annotation.SuppressLint;
@@ -83,9 +84,9 @@ public final class SurfaceControl {
private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {
if (getBuiltInDisplayMethod == null) {
// the method signature has changed in Android Q
// the method signature has changed in Android 10
// <https://github.com/Genymobile/scrcpy/issues/586>
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
} else {
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
@@ -106,7 +107,7 @@ public final class SurfaceControl {
public static IBinder getBuiltInDisplay() {
try {
Method method = getGetBuiltInDisplayMethod();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
// call getBuiltInDisplay(0)
return (IBinder) method.invoke(null, 0);
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import android.annotation.TargetApi;
@@ -200,7 +201,7 @@ public final class WindowManager {
}
}
@TargetApi(29)
@TargetApi(AndroidVersions.API_29_ANDROID_10)
public void registerDisplayFoldListener(IDisplayFoldListener foldListener) {
try {
Class<?> cls = manager.getClass();