Use explicit constants for Android versions

Who remembers code names? This avoids to check the mapping every time.
This commit is contained in:
Romain Vimont 2024-10-20 13:01:52 +02:00
parent e33be3d288
commit 3acffaae57
23 changed files with 108 additions and 62 deletions

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_20_ANDROID_4_4 = Build.VERSION_CODES.KITKAT_WATCH;
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;
}

View File

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

View File

@ -121,7 +121,7 @@ public final class Server {
} }
private static void scrcpy(Options options) throws IOException, ConfigurationException { 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"); Ln.e("Camera mirroring is not supported before Android 12");
throw new ConfigurationException("Camera mirroring is not supported"); throw new ConfigurationException("Camera mirroring is not supported");
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.control; package com.genymobile.scrcpy.control;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor; import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.CleanUp; import com.genymobile.scrcpy.CleanUp;
import com.genymobile.scrcpy.device.Device; 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> * 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 (action == MotionEvent.ACTION_DOWN) {
if (actionButton == buttons) { if (actionButton == buttons) {
// First button pressed: ACTION_DOWN // First button pressed: ACTION_DOWN
@ -423,7 +424,7 @@ public class Controller implements AsyncProcessor {
private void getClipboard(int copyKey) { private void getClipboard(int copyKey) {
// On Android >= 7, press the COPY or CUT key if requested // 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; 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 // 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); 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 // 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); device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC);
} }

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.util; package com.genymobile.scrcpy.util;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.BuildConfig; import com.genymobile.scrcpy.BuildConfig;
import android.os.Build; import android.os.Build;
@ -31,7 +32,7 @@ public final class IO {
} }
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException { 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()) { while (from.hasRemaining()) {
write(fd, from); write(fd, from);
} }

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.util; package com.genymobile.scrcpy.util;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
@ -34,7 +35,7 @@ public final class Settings {
} }
public static String getValue(String table, String key) throws SettingsException { 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> // on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key); 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 { 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> // on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value); 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 { 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> // on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key); String oldValue = provider.getValue(table, key);

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.video; package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor; import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.device.ConfigurationException; import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Size; 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 // 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_FRAME_RATE, 60);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 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_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
} }
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -24,7 +25,7 @@ public final class PowerManager {
private Method getIsScreenOnMethod() throws NoSuchMethodException { private Method getIsScreenOnMethod() throws NoSuchMethodException {
if (isScreenOnMethod == null) { if (isScreenOnMethod == null) {
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn"; String methodName = Build.VERSION.SDK_INT >= AndroidVersions.API_20_ANDROID_4_4 ? "isInteractive" : "isScreenOn";
isScreenOnMethod = manager.getClass().getMethod(methodName); isScreenOnMethod = manager.getClass().getMethod(methodName);
} }
return isScreenOnMethod; return isScreenOnMethod;

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -83,9 +84,9 @@ public final class SurfaceControl {
private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException { private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {
if (getBuiltInDisplayMethod == null) { 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> // <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); getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
} else { } else {
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken"); getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
@ -106,7 +107,7 @@ public final class SurfaceControl {
public static IBinder getBuiltInDisplay() { public static IBinder getBuiltInDisplay() {
try { try {
Method method = getGetBuiltInDisplayMethod(); 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) // call getBuiltInDisplay(0)
return (IBinder) method.invoke(null, 0); return (IBinder) method.invoke(null, 0);
} }