Compare commits
8 Commits
camera.22
...
android-fr
Author | SHA1 | Date | |
---|---|---|---|
f996386b6e | |||
cfc9882897 | |||
e4c152b1a3 | |||
6c5b20fdb1 | |||
512ef4e5c0 | |||
186a5fdcff | |||
fb3d09b7e3 | |||
ce3d7507ce |
@ -14,8 +14,8 @@ set -e
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=2.0
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-33}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
|
||||
PLATFORM=${ANDROID_PLATFORM:-23}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-23.0.3}
|
||||
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
|
||||
|
||||
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||
@ -43,6 +43,17 @@ public final class BuildConfig {
|
||||
}
|
||||
EOF
|
||||
|
||||
STUBS_DIR="$BUILD_DIR/stubs"
|
||||
rm -rf "$STUBS_DIR"
|
||||
mkdir -p "$STUBS_DIR"
|
||||
echo "Generating SDK stubs..."
|
||||
cd "$SERVER_DIR/src/main/stubs"
|
||||
javac -bootclasspath "$ANDROID_JAR" \
|
||||
-d "$STUBS_DIR" \
|
||||
-source 1.8 -target 1.8 \
|
||||
android/content/*
|
||||
cd -
|
||||
|
||||
echo "Generating java from aidl..."
|
||||
cd "$SERVER_DIR/src/main/aidl"
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl
|
||||
@ -52,7 +63,7 @@ cd "$SERVER_DIR/src/main/aidl"
|
||||
echo "Compiling java sources..."
|
||||
cd ../java
|
||||
javac -bootclasspath "$ANDROID_JAR" \
|
||||
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
||||
-cp "$LAMBDA_JAR:$GEN_DIR:$STUBS_DIR" \
|
||||
-d "$CLASSES_DIR" \
|
||||
-source 1.8 -target 1.8 \
|
||||
com/genymobile/scrcpy/*.java \
|
||||
|
@ -5,6 +5,7 @@ import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
@ -14,6 +15,7 @@ import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class AudioCapture {
|
||||
@ -42,13 +44,28 @@ public final class AudioCapture {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Method setBuilderContext;
|
||||
|
||||
@TargetApi(23)
|
||||
private static void setBuilderContext(AudioRecord.Builder builder, Context context) {
|
||||
try {
|
||||
if (setBuilderContext == null) {
|
||||
setBuilderContext = AudioRecord.Builder.class.getMethod("setContext", Context.class);
|
||||
}
|
||||
setBuilderContext.invoke(builder, context);
|
||||
} catch (Exception e) {
|
||||
Ln.e("Could not call AudioRecord.Builder.setContext() method");
|
||||
//throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@SuppressLint({"WrongConstant", "MissingPermission"})
|
||||
private static AudioRecord createAudioRecord() {
|
||||
AudioRecord.Builder builder = new AudioRecord.Builder();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
|
||||
builder.setContext(FakeContext.get());
|
||||
setBuilderContext(builder, FakeContext.get());
|
||||
}
|
||||
builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
|
||||
builder.setAudioFormat(createAudioFormat());
|
||||
@ -102,7 +119,7 @@ public final class AudioCapture {
|
||||
}
|
||||
|
||||
public void start() throws AudioCaptureForegroundException {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
if (Build.VERSION.SDK_INT == 30) {
|
||||
startWorkaroundAndroid11();
|
||||
try {
|
||||
tryStartRecording(3, 100);
|
||||
@ -121,7 +138,21 @@ public final class AudioCapture {
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
private static Method getTimestampMethod;
|
||||
|
||||
private static int getRecorderTimestamp(AudioRecord recorder, AudioTimestamp timestamp) {
|
||||
try {
|
||||
if (getTimestampMethod == null) {
|
||||
getTimestampMethod = AudioRecord.class.getMethod("getTimestamp", AudioTimestamp.class, int.class);
|
||||
}
|
||||
return (int) getTimestampMethod.invoke(recorder, timestamp, 0);
|
||||
} catch (Exception e) {
|
||||
Ln.e("Could not call AudioRecord.getTimestamp() method");
|
||||
return AudioRecord.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(24)
|
||||
public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) {
|
||||
int r = recorder.read(directBuffer, size);
|
||||
if (r <= 0) {
|
||||
@ -130,7 +161,7 @@ public final class AudioCapture {
|
||||
|
||||
long pts;
|
||||
|
||||
int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC);
|
||||
int ret = getRecorderTimestamp(recorder, timestamp);
|
||||
if (ret == AudioRecord.SUCCESS) {
|
||||
pts = timestamp.nanoTime / 1000;
|
||||
} else {
|
||||
|
@ -84,7 +84,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
return format;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
@TargetApi(24)
|
||||
private void inputThread(MediaCodec mediaCodec, AudioCapture capture) throws IOException, InterruptedException {
|
||||
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
|
||||
@ -159,7 +159,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public void encode() throws IOException, ConfigurationException, AudioCaptureForegroundException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
if (Build.VERSION.SDK_INT < 30) {
|
||||
Ln.w("Audio disabled: it is not supported before Android 11");
|
||||
streamer.writeDisableStream(false);
|
||||
return;
|
||||
@ -290,7 +290,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
}
|
||||
|
||||
private class EncoderCallback extends MediaCodec.Callback {
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
@TargetApi(24)
|
||||
@Override
|
||||
public void onInputBufferAvailable(MediaCodec codec, int index) {
|
||||
try {
|
||||
|
@ -373,8 +373,8 @@ 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()) {
|
||||
int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT;
|
||||
if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= 24 && device.supportsInputEvents()) {
|
||||
int key = copyKey == ControlMessage.COPY_KEY_COPY ? 278 : 277;
|
||||
// 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);
|
||||
}
|
||||
@ -397,8 +397,8 @@ 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()) {
|
||||
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC);
|
||||
if (paste && Build.VERSION.SDK_INT >= 24 && device.supportsInputEvents()) {
|
||||
device.pressReleaseKeycode(279, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
if (sequence != ControlMessage.SEQUENCE_INVALID) {
|
||||
|
@ -68,7 +68,8 @@ public final class DesktopConnection implements Closeable {
|
||||
LocalSocket controlSocket = null;
|
||||
try {
|
||||
if (tunnelForward) {
|
||||
try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) {
|
||||
LocalServerSocket localServerSocket = new LocalServerSocket(socketName);
|
||||
try {
|
||||
videoSocket = localServerSocket.accept();
|
||||
if (sendDummyByte) {
|
||||
// send one byte so the client may read() to detect a connection error
|
||||
@ -80,6 +81,8 @@ public final class DesktopConnection implements Closeable {
|
||||
if (control) {
|
||||
controlSocket = localServerSocket.accept();
|
||||
}
|
||||
} finally {
|
||||
localServerSocket.close();
|
||||
}
|
||||
} else {
|
||||
videoSocket = connect(socketName);
|
||||
|
@ -124,7 +124,7 @@ public final class Device {
|
||||
}
|
||||
|
||||
// main display or any display on Android >= Q
|
||||
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
|
||||
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= 29;
|
||||
if (!supportsInputEvents) {
|
||||
Ln.w("Input events are not supported for secondary displays before Android 10");
|
||||
}
|
||||
@ -173,7 +173,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 >= 29;
|
||||
}
|
||||
|
||||
public boolean supportsInputEvents() {
|
||||
@ -277,7 +277,7 @@ public final class Device {
|
||||
* @param mode one of the {@code POWER_MODE_*} constants
|
||||
*/
|
||||
public static boolean setScreenPowerMode(int mode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
// Change the power mode for all physical displays
|
||||
long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds();
|
||||
if (physicalDisplayIds == null) {
|
||||
|
@ -26,15 +26,13 @@ public final class FakeContext extends ContextWrapper {
|
||||
return PACKAGE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOpPackageName() {
|
||||
return PACKAGE_NAME;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.S)
|
||||
@Override
|
||||
@TargetApi(31)
|
||||
public AttributionSource getAttributionSource() {
|
||||
AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID);
|
||||
AttributionSource.Builder builder = new AttributionSource.Builder(0);
|
||||
builder.setPackageName(PACKAGE_NAME);
|
||||
return builder.build();
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
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"
|
||||
boolean secure = Build.VERSION.SDK_INT < 30 || (Build.VERSION.SDK_INT == 30 && !"S"
|
||||
.equals(Build.VERSION.CODENAME));
|
||||
return SurfaceControl.createDisplay("scrcpy", secure);
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ public final class Server {
|
||||
// Before Android 11, audio is not supported.
|
||||
// Since Android 12, we can properly set a context on the AudioRecord.
|
||||
// Only on Android 11 we must fill the application context for the AudioRecord to work.
|
||||
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
if (audio && Build.VERSION.SDK_INT == 30) {
|
||||
Workarounds.fillAppContext();
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,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 <= 30) {
|
||||
// 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 +47,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 <= 30) {
|
||||
// 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 +60,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 <= 30) {
|
||||
// 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);
|
||||
|
@ -7,7 +7,7 @@ public enum VideoCodec implements Codec {
|
||||
H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC),
|
||||
H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC),
|
||||
@SuppressLint("InlinedApi") // introduced in API 21
|
||||
AV1(0x00_61_76_31, "av1", MediaFormat.MIMETYPE_VIDEO_AV1);
|
||||
AV1(0x00_61_76_31, "av1", "video/av01");
|
||||
|
||||
private final int id; // 4-byte ASCII representation of the name
|
||||
private final String name;
|
||||
|
@ -51,7 +51,7 @@ public class ActivityManager {
|
||||
return removeContentProviderExternalMethod;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.Q)
|
||||
@TargetApi(29)
|
||||
private ContentProvider getContentProviderExternal(String name, IBinder token) {
|
||||
try {
|
||||
Method method = getGetContentProviderExternalMethod();
|
||||
|
@ -26,7 +26,7 @@ public class ClipboardManager {
|
||||
|
||||
private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
|
||||
if (getPrimaryClipMethod == null) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
|
||||
} else {
|
||||
try {
|
||||
@ -48,7 +48,7 @@ public class ClipboardManager {
|
||||
|
||||
private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
|
||||
if (setPrimaryClipMethod == null) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
|
||||
} else {
|
||||
try {
|
||||
@ -71,7 +71,7 @@ public class ClipboardManager {
|
||||
|
||||
private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ public class ClipboardManager {
|
||||
|
||||
private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME);
|
||||
return;
|
||||
}
|
||||
@ -133,7 +133,7 @@ public class ClipboardManager {
|
||||
|
||||
private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager,
|
||||
IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
|
||||
return;
|
||||
}
|
||||
@ -153,7 +153,7 @@ public class ClipboardManager {
|
||||
|
||||
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
|
||||
if (addPrimaryClipChangedListener == null) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
addPrimaryClipChangedListener = manager.getClass()
|
||||
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
|
||||
} else {
|
||||
|
@ -54,7 +54,7 @@ public 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 >= 31) {
|
||||
callMethod = provider.getClass().getMethod("call", AttributionSource.class, String.class, String.class, String.class, Bundle.class);
|
||||
callMethodVersion = 0;
|
||||
} else {
|
||||
@ -83,7 +83,7 @@ public 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 >= 31 && callMethodVersion == 0) {
|
||||
args = new Object[]{FakeContext.get().getAttributionSource(), "settings", callMethod, arg, extras};
|
||||
} else {
|
||||
switch (callMethodVersion) {
|
||||
|
@ -90,7 +90,7 @@ public final class SurfaceControl {
|
||||
if (getBuiltInDisplayMethod == null) {
|
||||
// the method signature has changed in Android Q
|
||||
// <https://github.com/Genymobile/scrcpy/issues/586>
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT < 29) {
|
||||
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
|
||||
} else {
|
||||
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
|
||||
@ -102,7 +102,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 < 29) {
|
||||
// call getBuiltInDisplay(0)
|
||||
return (IBinder) method.invoke(null, 0);
|
||||
}
|
||||
|
15
server/src/main/stubs/android/content/AttributionSource.java
Normal file
15
server/src/main/stubs/android/content/AttributionSource.java
Normal file
@ -0,0 +1,15 @@
|
||||
package android.content;
|
||||
|
||||
public class AttributionSource {
|
||||
public static class Builder {
|
||||
public Builder(int uid) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public Builder setPackageName(String value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public AttributionSource build() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user