Replace shadowed android.system with custom androidx.system package

So that we don't shadow framework classes in our binary.
Update buffer position in OsCompat.write (fixed in Lollipop MR1).
Synthesize InterruptedIOException in Kitkat like in Lollipop.
OsCompat never throws EINTR errno.
This commit is contained in:
Eugen Pechanec 2022-06-27 03:45:09 +02:00
parent c57ff1dc2a
commit a82a1d837e
No known key found for this signature in database
GPG key ID: 1E110C960179440E
16 changed files with 321 additions and 86 deletions

View file

@ -154,10 +154,4 @@ page at http://checkstyle.sourceforge.net/config.html -->
<module name="UpperEll" />
</module>
<!-- Excludes android.system polyfill. -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value=".*[\\/]java[\\/]android[\\/]system[\\/].*$" />
</module>
</module>

View file

@ -19,7 +19,6 @@ import java.io.FileDescriptor;
import java.nio.ByteBuffer;
public interface Os {
public String strerror(int errno);
public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
String strerror(int errno);
int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
}

24
os-compat/build.gradle Normal file
View file

@ -0,0 +1,24 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 31
defaultConfig {
minSdkVersion 19
targetSdkVersion 31
}
androidResources {
namespace = "androidx.system"
}
buildFeatures {
aidl = false
androidResources = false
buildConfig = false
}
}
dependencies {
compileOnly rootProject.fileTree("thirdparty/androidx/annotation/1.3.0/annotation-1.3.0.jar")
compileOnly project(':libcore')
}
apply from: "$project.rootDir/config/android-checkstyle.gradle"

View file

@ -13,30 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.system;
import android.annotation.TargetApi;
package androidx.system;
import java.io.IOException;
import java.net.SocketException;
import libcore.io.Libcore;
/**
* A checked exception thrown when {@link Os} methods fail. This exception contains the native
* errno value, for comparison against the constants in {@link OsConstants}, should sophisticated
* A checked exception thrown when {@link OsCompat} methods fail. This exception contains the native
* errno value, for comparison against the constants in {@link OsConstantsCompat}, should sophisticated
* callers need to adjust their behavior based on the exact failure.
*/
@SuppressWarnings("unused")
@TargetApi(21)
public final class ErrnoException extends Exception {
private final String functionName;
/**
* The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
*/
public final int errno;
private final int errno;
/**
* Constructs an instance with the given function name and errno value.
@ -55,17 +44,24 @@ public final class ErrnoException extends Exception {
this.errno = errno;
}
/**
* The errno value, for comparison with the {@code E} constants in {@link OsConstantsCompat}.
*/
public int getErrno() {
return errno;
}
/**
* Converts the stashed function name and errno value to a human-readable string.
* We do this here rather than in the constructor so that callers only pay for
* this if they need it.
*/
@Override public String getMessage() {
String errnoName = OsConstants.errnoName(errno);
String errnoName = OsConstantsCompat.errnoName(errno);
if (errnoName == null) {
errnoName = "errno " + errno;
}
String description = Libcore.os.strerror(errno);
String description = OsCompat.strerror(errno);
return functionName + " failed: " + errnoName + " (" + description + ")";
}
@ -77,9 +73,7 @@ public final class ErrnoException extends Exception {
* {@code throw e.rethrowAsIOException()} to make that clear to the compiler.
*/
public IOException rethrowAsIOException() throws IOException {
IOException newException = new IOException(getMessage());
newException.initCause(this);
throw newException;
throw new IOException(getMessage(), this);
}
/**

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -13,41 +13,46 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.system;
import android.annotation.TargetApi;
package androidx.system;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import libcore.io.Libcore;
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.
*
* <p>The corresponding constants can be found in {@link OsConstants}.
* <p>The corresponding constants can be found in {@link OsConstantsCompat}.
*/
@SuppressWarnings("unused")
@TargetApi(21)
public final class Os {
private Os() {}
/**
* See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
*/
public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
try {
return Libcore.os.write(fd, buffer);
} catch (libcore.io.ErrnoException e) {
throw new ErrnoException("write", e.errno);
public final class OsCompat {
private OsCompat() {
}
private static final Os IMPL;
static {
if (SDK_INT >= 21) {
IMPL = new OsApi21();
} else {
IMPL = new OsLibcore();
}
}
/**
* See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>.
*/
public static String strerror(int errno) {
return IMPL.strerror(errno);
}
/**
* See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
*/
public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
return IMPL.write(fd, buffer);
}
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -13,23 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.system;
package androidx.system;
import android.annotation.TargetApi;
import android.system.Os;
@SuppressWarnings("unused")
@TargetApi(21)
public final class OsConstants {
import static android.os.Build.VERSION.SDK_INT;
private OsConstants() { }
public final class OsConstantsCompat {
private OsConstantsCompat() {
}
public static final int EINTR = libcore.io.OsConstants.EINTR;
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 libcore.io.OsConstants.errnoName(errno);
return IMPL.errnoName(errno);
}
}

View file

@ -0,0 +1,8 @@
package androidx.system;
final class OsConstantsLibcore implements OsConstants {
@Override
public String errnoName(int errno) {
return libcore.io.OsConstants.errnoName(errno);
}
}

View file

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

View file

@ -22,7 +22,7 @@ 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")
compileOnly project(':libcore')
implementation project(':os-compat')
}
apply from: "$project.rootDir/config/android-checkstyle.gradle"

View file

@ -52,10 +52,12 @@ echo "Compiling java sources..."
cd ../java
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 \
android/system/*.java \
$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
@ -68,7 +70,7 @@ then
# use dx
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
android/system/*.class \
androidx/system/*.class \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
@ -82,7 +84,7 @@ else
# use d8
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
--output "$BUILD_DIR/classes.zip" \
android/system/*.class \
androidx/system/*.class \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \

View file

@ -1,9 +1,7 @@
package com.genymobile.scrcpy;
import android.annotation.TargetApi;
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,25 +12,13 @@ public final class IO {
// not instantiable
}
@TargetApi(21) // Backported subset of android.system.
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 <https://github.com/Genymobile/scrcpy/issues/291>.
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);
}
try {
while (from.hasRemaining()) {
OsCompat.write(fd, from);
}
} catch (ErrnoException e) {
throw new IOException(e);
}
}

View file

@ -1,2 +1,3 @@
include ':server'
include ':os-compat'
include ':libcore'