diff --git a/README.md b/README.md
index db531c6100..7f6af3dd53 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ Its features include:
## Requirements
-The Android device requires at least API 21 (Android 5.0).
+The Android device requires at least API 19 (Android 4.4).
Make sure you [enable adb debugging][enable-adb] on your device(s).
diff --git a/app/src/server.c b/app/src/server.c
index 663ef18bb4..ba00b14f6c 100644
--- a/app/src/server.c
+++ b/app/src/server.c
@@ -18,6 +18,7 @@
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
+#define SC_DEVICE_ANDROID_DATA_PATH "/data/local/tmp"
static char *
get_server_path(void) {
@@ -166,6 +167,9 @@ execute_server(struct sc_server *server,
cmd[count++] = "-s";
cmd[count++] = serial;
cmd[count++] = "shell";
+ cmd[count++] = "mkdir -p " SC_DEVICE_ANDROID_DATA_PATH "/dalvik-cache";
+ cmd[count++] = "&&";
+ cmd[count++] = "ANDROID_DATA=" SC_DEVICE_ANDROID_DATA_PATH;
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
cmd[count++] = "app_process";
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index edda3919af..ca0d813b4a 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -152,7 +152,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
This method always terminates by throwing the exception. Callers can write + * {@code throw e.rethrowAsIOException()} to make that clear to the compiler. + */ + public IOException rethrowAsIOException() throws IOException { + throw new IOException(getMessage(), this); + } + + /** + * Throws a {@link SocketException} with a message based on {@link #getMessage()} and with this + * instance as the cause. + * + *
This method always terminates by throwing the exception. Callers can write + * {@code throw e.rethrowAsIOException()} to make that clear to the compiler. + */ + public SocketException rethrowAsSocketException() throws SocketException { + final SocketException newException = new SocketException(getMessage()); + newException.initCause(this); + throw newException; + } +} diff --git a/os-compat/src/main/java/androidx/system/Os.java b/os-compat/src/main/java/androidx/system/Os.java new file mode 100644 index 0000000000..32d6b495d6 --- /dev/null +++ b/os-compat/src/main/java/androidx/system/Os.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 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 androidx.system; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; + +interface Os { + String strerror(int errno); + + int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException; + + // https://android.googlesource.com/platform/libcore/+/lollipop-mr1-release/luni/src/main/java/libcore/io/Posix.java#253 + static void maybeUpdateBufferPosition(ByteBuffer buffer, int originalPosition, int bytesReadOrWritten) { + if (bytesReadOrWritten > 0) { + buffer.position(bytesReadOrWritten + originalPosition); + } + } +} diff --git a/os-compat/src/main/java/androidx/system/OsApi21.java b/os-compat/src/main/java/androidx/system/OsApi21.java new file mode 100644 index 0000000000..973aa4df5c --- /dev/null +++ b/os-compat/src/main/java/androidx/system/OsApi21.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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 androidx.system; + +import androidx.annotation.RequiresApi; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; + +import static android.os.Build.VERSION.SDK_INT; +import static androidx.system.Os.maybeUpdateBufferPosition; + +@RequiresApi(21) +final class OsApi21 implements Os { + @Override + public String strerror(int errno) { + return android.system.Os.strerror(errno); + } + + @Override + public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { + try { + final int position = buffer.position(); + final int bytesWritten = android.system.Os.write(fd, buffer); + if (SDK_INT < 22) { + maybeUpdateBufferPosition(buffer, position, bytesWritten); + } + return bytesWritten; + } catch (android.system.ErrnoException e) { + throw new ErrnoException("write", e.errno); + } + } +} diff --git a/os-compat/src/main/java/androidx/system/OsCompat.java b/os-compat/src/main/java/androidx/system/OsCompat.java new file mode 100644 index 0000000000..4df5c736c2 --- /dev/null +++ b/os-compat/src/main/java/androidx/system/OsCompat.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 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 androidx.system; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; + +import static android.os.Build.VERSION.SDK_INT; + +/** + * Access to low-level system functionality. Most of these are system calls. Most users will want + * to use higher-level APIs where available, but this class provides access to the underlying + * primitives used to implement the higher-level APIs. + * + *
The corresponding constants can be found in {@link OsConstantsCompat}.
+ */
+public final class OsCompat {
+ private OsCompat() {
+ }
+
+ private static final Os IMPL;
+
+ static {
+ if (SDK_INT >= 21) {
+ IMPL = new OsApi21();
+ } else {
+ IMPL = new OsLibcore();
+ }
+ }
+
+ /**
+ * See strerror(2).
+ */
+ public static String strerror(int errno) {
+ return IMPL.strerror(errno);
+ }
+
+ /**
+ * See write(2).
+ */
+ public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
+ return IMPL.write(fd, buffer);
+ }
+}
diff --git a/os-compat/src/main/java/androidx/system/OsConstants.java b/os-compat/src/main/java/androidx/system/OsConstants.java
new file mode 100644
index 0000000000..efcd3c3c18
--- /dev/null
+++ b/os-compat/src/main/java/androidx/system/OsConstants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2011 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 androidx.system;
+
+interface OsConstants {
+
+ String errnoName(int errno);
+}
diff --git a/os-compat/src/main/java/androidx/system/OsConstantsApi21.java b/os-compat/src/main/java/androidx/system/OsConstantsApi21.java
new file mode 100644
index 0000000000..2108c749df
--- /dev/null
+++ b/os-compat/src/main/java/androidx/system/OsConstantsApi21.java
@@ -0,0 +1,11 @@
+package androidx.system;
+
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(21)
+final class OsConstantsApi21 implements OsConstants {
+ @Override
+ public String errnoName(int errno) {
+ return android.system.OsConstants.errnoName(errno);
+ }
+}
diff --git a/os-compat/src/main/java/androidx/system/OsConstantsCompat.java b/os-compat/src/main/java/androidx/system/OsConstantsCompat.java
new file mode 100644
index 0000000000..e753ea5d65
--- /dev/null
+++ b/os-compat/src/main/java/androidx/system/OsConstantsCompat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 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 androidx.system;
+
+import android.system.Os;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+public final class OsConstantsCompat {
+ private OsConstantsCompat() {
+ }
+
+ private static final OsConstants IMPL;
+
+ static {
+ if (SDK_INT >= 21) {
+ IMPL = new OsConstantsApi21();
+ } else {
+ IMPL = new OsConstantsLibcore();
+ }
+ }
+
+ /**
+ * Returns the string name of an errno value.
+ * For example, "EACCES". See {@link Os#strerror} for human-readable errno descriptions.
+ */
+ public static String errnoName(int errno) {
+ return IMPL.errnoName(errno);
+ }
+}
diff --git a/os-compat/src/main/java/androidx/system/OsConstantsLibcore.java b/os-compat/src/main/java/androidx/system/OsConstantsLibcore.java
new file mode 100644
index 0000000000..2473d7e539
--- /dev/null
+++ b/os-compat/src/main/java/androidx/system/OsConstantsLibcore.java
@@ -0,0 +1,8 @@
+package androidx.system;
+
+final class OsConstantsLibcore implements OsConstants {
+ @Override
+ public String errnoName(int errno) {
+ return libcore.io.OsConstants.errnoName(errno);
+ }
+}
diff --git a/os-compat/src/main/java/androidx/system/OsLibcore.java b/os-compat/src/main/java/androidx/system/OsLibcore.java
new file mode 100644
index 0000000000..679f9d2aef
--- /dev/null
+++ b/os-compat/src/main/java/androidx/system/OsLibcore.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 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 androidx.system;
+
+import libcore.io.Libcore;
+import libcore.io.OsConstants;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+
+import static androidx.system.Os.maybeUpdateBufferPosition;
+
+final class OsLibcore implements Os {
+ @Override
+ public String strerror(int errno) {
+ return Libcore.os.strerror(errno);
+ }
+
+ @Override
+ public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
+ try {
+ return ioFailureRetry("write", fd, () -> {
+ final int position = buffer.position();
+ final int bytesWritten = Libcore.os.write(fd, buffer);
+ maybeUpdateBufferPosition(buffer, position, bytesWritten);
+ return bytesWritten;
+ });
+ } catch (libcore.io.ErrnoException e) {
+ throw new ErrnoException("write", e.errno);
+ }
+ }
+
+ // https://android.googlesource.com/platform/libcore/+/lollipop-release/luni/src/main/native/libcore_io_Posix.cpp#128
+ // https://android.googlesource.com/platform/libcore/+/kitkat-release/luni/src/main/java/libcore/io/IoBridge.java#186
+ private static int ioFailureRetry(String functionName, FileDescriptor fd, SysCall syscall)
+ throws libcore.io.ErrnoException, InterruptedIOException {
+ if (!fd.valid()) {
+ throw UndeclaredExceptions.raise(new IOException("File descriptor closed"));
+ }
+ int rc = -1;
+ do {
+ int syscallErrno = 0;
+ try {
+ rc = syscall.call();
+ } catch (libcore.io.ErrnoException e) {
+ syscallErrno = e.errno;
+ }
+ if (rc == -1 && !fd.valid()) {
+ throw new InterruptedIOException(functionName + " interrupted");
+ }
+ if (rc == -1 && syscallErrno != OsConstants.EINTR) {
+ throw new libcore.io.ErrnoException(functionName, syscallErrno);
+ }
+ } while (rc == -1);
+ return rc;
+ }
+
+ @FunctionalInterface
+ private interface SysCall {
+
+ Integer call() throws libcore.io.ErrnoException;
+ }
+
+ // https://dzone.com/articles/throwing-undeclared-checked
+ private static final class UndeclaredExceptions extends RuntimeException {
+ private static Throwable sThrowable = null;
+
+ public static synchronized RuntimeException raise(Throwable throwable) {
+ if (throwable instanceof ReflectiveOperationException || throwable instanceof RuntimeException) {
+ throw new IllegalArgumentException("Unsupported exception: " + throwable.getClass());
+ }
+
+ sThrowable = throwable;
+ try {
+ return UndeclaredExceptions.class.newInstance();
+ } catch (ReflectiveOperationException e) {
+ return new RuntimeException(e);
+ } finally {
+ sThrowable = null;
+ }
+ }
+
+ private UndeclaredExceptions() throws Throwable {
+ throw sThrowable;
+ }
+ }
+}
diff --git a/server/build.gradle b/server/build.gradle
index dbc8261f68..58758438bf 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -4,7 +4,7 @@ android {
compileSdkVersion 31
defaultConfig {
applicationId "com.genymobile.scrcpy"
- minSdkVersion 21
+ minSdkVersion 19
targetSdkVersion 31
versionCode 12400
versionName "1.24"
@@ -21,6 +21,8 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.1'
+ compileOnly rootProject.fileTree("thirdparty/androidx/annotation/1.3.0/annotation-1.3.0.jar")
+ implementation project(':os-compat')
}
apply from: "$project.rootDir/config/android-checkstyle.gradle"
diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh
index c881e38a8b..f67391a780 100755
--- a/server/build_without_gradle.sh
+++ b/server/build_without_gradle.sh
@@ -20,12 +20,14 @@ BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes"
SERVER_DIR=$(dirname "$0")
+ROOT_PROJECT_DIR=$(realpath $SERVER_DIR/..)
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"
+echo "Root project dir: $ROOT_PROJECT_DIR"
rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
@@ -48,8 +50,15 @@ cd "$SERVER_DIR/src/main/aidl"
echo "Compiling java sources..."
cd ../java
-javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \
+classpath="$CLASSES_DIR"
+classpath="$classpath:$ROOT_PROJECT_DIR/thirdparty/androidx/annotation/1.3.0/annotation-1.3.0.jar"
+# https://stackoverflow.com/a/58768648/2444099
+classpath="$classpath:$ANDROID_HOME/build-tools/$BUILD_TOOLS/core-lambda-stubs.jar"
+javac -bootclasspath "$ANDROID_JAR" -cp "$classpath" -d "$CLASSES_DIR" \
+ -encoding UTF-8 \
-source 1.8 -target 1.8 \
+ $ROOT_PROJECT_DIR/os-compat/src/main/java/androidx/system/*.java \
+ $ROOT_PROJECT_DIR/libcore/src/main/java/libcore/io/*.java \
com/genymobile/scrcpy/*.java \
com/genymobile/scrcpy/wrappers/*.java
@@ -61,6 +70,7 @@ then
# use dx
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
+ androidx/system/*.class \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
@@ -74,6 +84,7 @@ else
# use d8
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
--output "$BUILD_DIR/classes.zip" \
+ androidx/system/*.class \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
diff --git a/server/src/main/java/com/genymobile/scrcpy/IO.java b/server/src/main/java/com/genymobile/scrcpy/IO.java
index 57c017dbee..b3b6c247e4 100644
--- a/server/src/main/java/com/genymobile/scrcpy/IO.java
+++ b/server/src/main/java/com/genymobile/scrcpy/IO.java
@@ -1,8 +1,7 @@
package com.genymobile.scrcpy;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
+import androidx.system.ErrnoException;
+import androidx.system.OsCompat;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -14,23 +13,12 @@ private IO() {
}
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