Compare commits

..
2 Commits
Author SHA1 Message Date
Romain Vimont 4f7869d130 Fix audio PTS when not monotonically increasing
Some decoders fail to guarantee that PTS must be monotonically
increasing. Fix the PTS when they're not.

Fixes #4054 <https://github.com/Genymobile/scrcpy/issues/4054>
2023-06-02 21:39:29 +02:00
Romain Vimont 0f28d39127 Do not add 1µS to the PTS in corner cases
The PTS must be monotonically increasing, but not strictly.
2023-06-02 21:38:30 +02:00
18 changed files with 70 additions and 111 deletions
+1 -2
View File
@@ -19,9 +19,8 @@ _scrcpy() {
-f --fullscreen -f --fullscreen
--force-adb-forward --force-adb-forward
--forward-all-clicks --forward-all-clicks
-h --help
--kill-adb-on-close
-K --hid-keyboard -K --hid-keyboard
-h --help
--legacy-paste --legacy-paste
--list-displays --list-displays
--list-encoders --list-encoders
+1 -2
View File
@@ -26,9 +26,8 @@ arguments=(
{-f,--fullscreen}'[Start in fullscreen]' {-f,--fullscreen}'[Start in fullscreen]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]' '--forward-all-clicks[Forward clicks to device]'
{-h,--help}'[Print the help]'
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
{-h,--help}'[Print the help]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-displays[List displays available on the device]' '--list-displays[List displays available on the device]'
'--list-encoders[List video and audio encoders available on the device]' '--list-encoders[List video and audio encoders available on the device]'
-4
View File
@@ -129,10 +129,6 @@ By default, right-click triggers BACK (or POWER on) and middle-click triggers HO
.B \-h, \-\-help .B \-h, \-\-help
Print this help. Print this help.
.TP
.B \-\-kill\-adb\-on\-close
Kill adb when scrcpy terminates.
.TP .TP
.B \-K, \-\-hid\-keyboard .B \-K, \-\-hid\-keyboard
Simulate a physical keyboard by using HID over AOAv2. Simulate a physical keyboard by using HID over AOAv2.
+5 -14
View File
@@ -77,7 +77,6 @@ enum {
OPT_NO_AUDIO_PLAYBACK, OPT_NO_AUDIO_PLAYBACK,
OPT_NO_VIDEO_PLAYBACK, OPT_NO_VIDEO_PLAYBACK,
OPT_AUDIO_SOURCE, OPT_AUDIO_SOURCE,
OPT_KILL_ADB_ON_CLOSE,
}; };
struct sc_option { struct sc_option {
@@ -276,16 +275,6 @@ static const struct sc_option options[] = {
"middle-click triggers HOME. This option disables these " "middle-click triggers HOME. This option disables these "
"shortcuts and forwards the clicks to the device instead.", "shortcuts and forwards the clicks to the device instead.",
}, },
{
.shortopt = 'h',
.longopt = "help",
.text = "Print this help.",
},
{
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
.longopt = "kill-adb-on-close",
.text = "Kill adb when scrcpy terminates.",
},
{ {
.shortopt = 'K', .shortopt = 'K',
.longopt = "hid-keyboard", .longopt = "hid-keyboard",
@@ -303,6 +292,11 @@ static const struct sc_option options[] = {
"is enabled (or a physical keyboard is connected).\n" "is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.", "Also see --hid-mouse.",
}, },
{
.shortopt = 'h',
.longopt = "help",
.text = "Print this help.",
},
{ {
.longopt_id = OPT_LEGACY_PASTE, .longopt_id = OPT_LEGACY_PASTE,
.longopt = "legacy-paste", .longopt = "legacy-paste",
@@ -1950,9 +1944,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case OPT_KILL_ADB_ON_CLOSE:
opts->kill_adb_on_close = true;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;
-1
View File
@@ -80,5 +80,4 @@ const struct scrcpy_options scrcpy_options_default = {
.require_audio = false, .require_audio = false,
.list_encoders = false, .list_encoders = false,
.list_displays = false, .list_displays = false,
.kill_adb_on_close = false,
}; };
-1
View File
@@ -180,7 +180,6 @@ struct scrcpy_options {
bool require_audio; bool require_audio;
bool list_encoders; bool list_encoders;
bool list_displays; bool list_displays;
bool kill_adb_on_close;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;
+41 -46
View File
@@ -96,30 +96,23 @@ sc_recorder_rescale_packet(AVStream *stream, AVPacket *packet) {
} }
static bool static bool
sc_recorder_write_stream(struct sc_recorder *recorder, sc_recorder_write_stream(struct sc_recorder *recorder, int stream_index,
struct sc_recorder_stream *st, AVPacket *packet) { AVPacket *packet) {
AVStream *stream = recorder->ctx->streams[st->index]; AVStream *stream = recorder->ctx->streams[stream_index];
sc_recorder_rescale_packet(stream, packet); sc_recorder_rescale_packet(stream, packet);
if (st->last_pts != AV_NOPTS_VALUE && packet->pts <= st->last_pts) {
LOGW("Fixing PTS non monotonically increasing in stream %d "
"(%" PRIi64 " >= %" PRIi64 ")",
st->index, st->last_pts, packet->pts);
packet->pts = ++st->last_pts;
packet->dts = packet->pts;
} else {
st->last_pts = packet->pts;
}
return av_interleaved_write_frame(recorder->ctx, packet) >= 0; return av_interleaved_write_frame(recorder->ctx, packet) >= 0;
} }
static inline bool static inline bool
sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) { sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) {
return sc_recorder_write_stream(recorder, &recorder->video_stream, packet); return sc_recorder_write_stream(recorder, recorder->video_stream_index,
packet);
} }
static inline bool static inline bool
sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) { sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) {
return sc_recorder_write_stream(recorder, &recorder->audio_stream, packet); return sc_recorder_write_stream(recorder, recorder->audio_stream_index,
packet);
} }
static bool static bool
@@ -185,11 +178,10 @@ static bool
sc_recorder_process_header(struct sc_recorder *recorder) { sc_recorder_process_header(struct sc_recorder *recorder) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && while (!recorder->stopped && (!recorder->video_init
((recorder->video && !recorder->video_init) || !recorder->audio_init
|| (recorder->audio && !recorder->audio_init) || sc_recorder_has_empty_queues(recorder))) {
|| sc_recorder_has_empty_queues(recorder))) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
sc_cond_wait(&recorder->cond, &recorder->mutex);
} }
if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) {
@@ -222,9 +214,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
goto end; goto end;
} }
assert(recorder->video_stream.index >= 0); assert(recorder->video_stream_index >= 0);
AVStream *video_stream = AVStream *video_stream =
recorder->ctx->streams[recorder->video_stream.index]; recorder->ctx->streams[recorder->video_stream_index];
bool ok = sc_recorder_set_extradata(video_stream, video_pkt); bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
if (!ok) { if (!ok) {
goto end; goto end;
@@ -237,9 +229,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
goto end; goto end;
} }
assert(recorder->audio_stream.index >= 0); assert(recorder->audio_stream_index >= 0);
AVStream *audio_stream = AVStream *audio_stream =
recorder->ctx->streams[recorder->audio_stream.index]; recorder->ctx->streams[recorder->audio_stream_index];
bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt); bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt);
if (!ok) { if (!ok) {
goto end; goto end;
@@ -297,7 +289,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
// A new packet may be assigned to audio_pkt and be processed // A new packet may be assigned to audio_pkt and be processed
break; break;
} }
sc_cond_wait(&recorder->cond, &recorder->mutex); sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
} }
// If stopped is set, continue to process the remaining events (to // If stopped is set, continue to process the remaining events (to
@@ -512,10 +504,10 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
return false; return false;
} }
recorder->video_stream.index = stream->index; recorder->video_stream_index = stream->index;
recorder->video_init = true; recorder->video_init = true;
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return true; return true;
@@ -530,7 +522,7 @@ sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
// EOS also stops the recorder // EOS also stops the recorder
recorder->stopped = true; recorder->stopped = true;
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
} }
@@ -556,7 +548,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
return false; return false;
} }
rec->stream_index = recorder->video_stream.index; rec->stream_index = recorder->video_stream_index;
bool ok = sc_vecdeque_push(&recorder->video_queue, rec); bool ok = sc_vecdeque_push(&recorder->video_queue, rec);
if (!ok) { if (!ok) {
@@ -565,7 +557,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
return false; return false;
} }
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return true; return true;
@@ -593,10 +585,10 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
return false; return false;
} }
recorder->audio_stream.index = stream->index; recorder->audio_stream_index = stream->index;
recorder->audio_init = true; recorder->audio_init = true;
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return true; return true;
@@ -612,7 +604,7 @@ sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
// EOS also stops the recorder // EOS also stops the recorder
recorder->stopped = true; recorder->stopped = true;
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
} }
@@ -639,7 +631,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
return false; return false;
} }
rec->stream_index = recorder->audio_stream.index; rec->stream_index = recorder->audio_stream_index;
bool ok = sc_vecdeque_push(&recorder->audio_queue, rec); bool ok = sc_vecdeque_push(&recorder->audio_queue, rec);
if (!ok) { if (!ok) {
@@ -648,7 +640,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
return false; return false;
} }
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return true; return true;
@@ -666,16 +658,10 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->audio = false; recorder->audio = false;
recorder->audio_init = true; recorder->audio_init = true;
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
} }
static void
sc_recorder_stream_init(struct sc_recorder_stream *stream) {
stream->index = -1;
stream->last_pts = AV_NOPTS_VALUE;
}
bool bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename, sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio, enum sc_record_format format, bool video, bool audio,
@@ -691,11 +677,16 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
goto error_free_filename; goto error_free_filename;
} }
ok = sc_cond_init(&recorder->cond); ok = sc_cond_init(&recorder->queue_cond);
if (!ok) { if (!ok) {
goto error_mutex_destroy; goto error_mutex_destroy;
} }
ok = sc_cond_init(&recorder->stream_cond);
if (!ok) {
goto error_queue_cond_destroy;
}
assert(video || audio); assert(video || audio);
recorder->video = video; recorder->video = video;
recorder->audio = audio; recorder->audio = audio;
@@ -707,8 +698,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
recorder->video_init = false; recorder->video_init = false;
recorder->audio_init = false; recorder->audio_init = false;
sc_recorder_stream_init(&recorder->video_stream); recorder->video_stream_index = -1;
sc_recorder_stream_init(&recorder->audio_stream); recorder->audio_stream_index = -1;
recorder->format = format; recorder->format = format;
@@ -739,6 +730,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
return true; return true;
error_queue_cond_destroy:
sc_cond_destroy(&recorder->queue_cond);
error_mutex_destroy: error_mutex_destroy:
sc_mutex_destroy(&recorder->mutex); sc_mutex_destroy(&recorder->mutex);
error_free_filename: error_free_filename:
@@ -763,7 +756,8 @@ void
sc_recorder_stop(struct sc_recorder *recorder) { sc_recorder_stop(struct sc_recorder *recorder) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->stopped = true; recorder->stopped = true;
sc_cond_signal(&recorder->cond); sc_cond_signal(&recorder->queue_cond);
sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
} }
@@ -774,7 +768,8 @@ sc_recorder_join(struct sc_recorder *recorder) {
void void
sc_recorder_destroy(struct sc_recorder *recorder) { sc_recorder_destroy(struct sc_recorder *recorder) {
sc_cond_destroy(&recorder->cond); sc_cond_destroy(&recorder->stream_cond);
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex); sc_mutex_destroy(&recorder->mutex);
free(recorder->filename); free(recorder->filename);
} }
+4 -8
View File
@@ -14,11 +14,6 @@
struct sc_recorder_queue SC_VECDEQUE(AVPacket *); struct sc_recorder_queue SC_VECDEQUE(AVPacket *);
struct sc_recorder_stream {
int index;
int64_t last_pts;
};
struct sc_recorder { struct sc_recorder {
struct sc_packet_sink video_packet_sink; struct sc_packet_sink video_packet_sink;
struct sc_packet_sink audio_packet_sink; struct sc_packet_sink audio_packet_sink;
@@ -40,18 +35,19 @@ struct sc_recorder {
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond cond; sc_cond queue_cond;
// set on sc_recorder_stop(), packet_sink close or recording failure // set on sc_recorder_stop(), packet_sink close or recording failure
bool stopped; bool stopped;
struct sc_recorder_queue video_queue; struct sc_recorder_queue video_queue;
struct sc_recorder_queue audio_queue; struct sc_recorder_queue audio_queue;
// wake up the recorder thread once the video or audio codec is known // wake up the recorder thread once the video or audio codec is known
sc_cond stream_cond;
bool video_init; bool video_init;
bool audio_init; bool audio_init;
struct sc_recorder_stream video_stream; int video_stream_index;
struct sc_recorder_stream audio_stream; int audio_stream_index;
const struct sc_recorder_callbacks *cbs; const struct sc_recorder_callbacks *cbs;
void *cbs_userdata; void *cbs_userdata;
-1
View File
@@ -364,7 +364,6 @@ scrcpy(struct scrcpy_options *options) {
.power_on = options->power_on, .power_on = options->power_on,
.list_encoders = options->list_encoders, .list_encoders = options->list_encoders,
.list_displays = options->list_displays, .list_displays = options->list_displays,
.kill_adb_on_close = options->kill_adb_on_close,
}; };
static const struct sc_server_callbacks cbs = { static const struct sc_server_callbacks cbs = {
+1 -13
View File
@@ -794,15 +794,6 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
return sc_server_connect_to_tcpip(server, ip_port); return sc_server_connect_to_tcpip(server, ip_port);
} }
static void
sc_server_kill_adb_if_requested(struct sc_server *server) {
if (server->params.kill_adb_on_close) {
LOGI("Killing adb server...");
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
sc_adb_kill_server(&server->intr, flags);
}
}
static int static int
run_server(void *data) { run_server(void *data) {
struct sc_server *server = data; struct sc_server *server = data;
@@ -814,7 +805,7 @@ run_server(void *data) {
// is parsed, so it is not output) // is parsed, so it is not output)
bool ok = sc_adb_start_server(&server->intr, 0); bool ok = sc_adb_start_server(&server->intr, 0);
if (!ok) { if (!ok) {
LOGE("Could not start adb server"); LOGE("Could not start adb daemon");
goto error_connection_failed; goto error_connection_failed;
} }
@@ -1002,12 +993,9 @@ run_server(void *data) {
sc_process_close(pid); sc_process_close(pid);
sc_server_kill_adb_if_requested(server);
return 0; return 0;
error_connection_failed: error_connection_failed:
sc_server_kill_adb_if_requested(server);
server->cbs->on_connection_failed(server, server->cbs_userdata); server->cbs->on_connection_failed(server, server->cbs_userdata);
return -1; return -1;
} }
-1
View File
@@ -58,7 +58,6 @@ struct sc_server_params {
bool power_on; bool power_on;
bool list_encoders; bool list_encoders;
bool list_displays; bool list_displays;
bool kill_adb_on_close;
}; };
struct sc_server { struct sc_server {
+1 -1
View File
@@ -83,7 +83,7 @@ scrcpy_otg(struct scrcpy_options *options) {
#ifdef _WIN32 #ifdef _WIN32
// On Windows, only one process could open a USB device // On Windows, only one process could open a USB device
// <https://github.com/Genymobile/scrcpy/issues/2773> // <https://github.com/Genymobile/scrcpy/issues/2773>
LOGI("Killing adb server (if any)..."); LOGI("Killing adb daemon (if any)...");
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR; unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
// uninterruptible (intr == NULL), but in practice it's very quick // uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags); sc_adb_kill_server(NULL, flags);
+1 -1
View File
@@ -56,7 +56,7 @@ For example, to use the device as a dictaphone and record a capture directly on
the computer: the computer:
``` ```
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus scrcpy --audio-source=mic --no-video --no-audio-playback --record=file.opus
``` ```
+1 -1
View File
@@ -17,7 +17,7 @@ To record only the audio:
```bash ```bash
scrcpy --no-video --record=file.opus scrcpy --no-video --record=file.opus
scrcpy --no-video --audio-codec=aac --record=file.aac scrcpy --no-video --audio-codec=aac --record-file=file.aac
# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac # .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac
``` ```
@@ -163,7 +163,7 @@ public final class AudioCapture {
// - an estimation from the previous PTS and the packet size as a fallback. // - an estimation from the previous PTS and the packet size as a fallback.
// //
// Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it.
pts = previousPts + 1; pts = previousPts;
} }
previousPts = pts; previousPts = pts;
@@ -105,8 +105,17 @@ public final class AudioEncoder implements AsyncProcessor {
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException { private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
streamer.writeAudioHeader(); streamer.writeAudioHeader();
long lastPts = 0;
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted()) {
OutputTask task = outputTasks.take(); OutputTask task = outputTasks.take();
if (task.bufferInfo.presentationTimeUs < lastPts) {
// Fix PTS if not monotonically increasing
task.bufferInfo.presentationTimeUs = lastPts;
} else {
lastPts = task.bufferInfo.presentationTimeUs;
}
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index); ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
try { try {
streamer.writePacket(buffer, task.bufferInfo); streamer.writePacket(buffer, task.bufferInfo);
@@ -14,13 +14,13 @@ public final class InputManager {
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
private final Object manager; private final android.hardware.input.InputManager manager;
private Method injectInputEventMethod; private Method injectInputEventMethod;
private static Method setDisplayIdMethod; private static Method setDisplayIdMethod;
private static Method setActionButtonMethod; private static Method setActionButtonMethod;
public InputManager(Object manager) { public InputManager(android.hardware.input.InputManager manager) {
this.manager = manager; this.manager = manager;
} }
@@ -62,21 +62,11 @@ public final class ServiceManager {
return displayManager; return displayManager;
} }
public static Class<?> getInputManagerClass() {
try {
// Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview
return Class.forName("android.hardware.input.InputManagerGlobal");
} catch (ClassNotFoundException e) {
return android.hardware.input.InputManager.class;
}
}
public static InputManager getInputManager() { public static InputManager getInputManager() {
if (inputManager == null) { if (inputManager == null) {
try { try {
Class<?> inputManagerClass = getInputManagerClass(); Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance"); android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
Object im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
inputManager = new InputManager(im); inputManager = new InputManager(im);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e); throw new AssertionError(e);