Compare commits

..

11 Commits
pr3523 ... rtp

Author SHA1 Message Date
Romain Vimont
0d298ad577 Plug RTP 2023-01-26 09:26:08 +01:00
Romain Vimont
0e34f4fbf7 Add RTP 2023-01-26 09:26:08 +01:00
Romain Vimont
b13aece7a1 Adapt ClipboardManager for Android 13
A new "attributionTag" parameter has been added to the methods
getPrimaryClip(), setPrimaryClip() and addPrimaryClipChangedListener()
of IClipboard.aidl.

Refs <0e3e509b3b%5E%21/>

Fixes #3497 <https://github.com/Genymobile/scrcpy/issues/3497>
2022-12-20 10:38:39 +01:00
Romain Vimont
bd1deffa70 Use current adb port (if any) for --tcpip
If the current adb port is not 5555 (typically 0 because it is not in
TCP/IP mode), --tcpip automatically executes (among other commands):

    adb tcpip 5555

In case adb was already listening on another port, this command forced
to listen on 5555, and the connection should still succeed.

But this reconfiguration might be inconvenient for the user. If adb is
already in TCP/IP mode, use the current enabled port without
reconfiguration.

Fixes #3591 <https://github.com/Genymobile/scrcpy/issues/3591>
2022-12-02 19:09:53 +01:00
Romain Vimont
6469b55861 Fix CommandParserTest code style
Make checkstyle happy.
2022-11-24 09:27:10 +01:00
SeungHoon Han
597703b62e Fix DisplayInfo parsing for Android Q
The DisplayInfo dump format has slightly changed in AOSP:
<1039ea50f3>

PR #3573 <https://github.com/Genymobile/scrcpy/pull/3573>
Ref #3416 <https://github.com/Genymobile/scrcpy/pull/3416>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-11-12 18:15:45 +01:00
Yu-Chen Lin
48bb6f2ea8 Support wchar_t in argv for Windows
PR #3547 <https://github.com/Genymobile/scrcpy/pull/3547>
Fixes #2932 <https://github.com/Genymobile/scrcpy/issues/2932>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-23 23:45:00 +02:00
Yu-Chen Lin
d71587e39b Avoid string concatenation in crossfiles
This feature is not supported on older meson versions:

    ERROR: Malformed value in cross file variable prebuilt_libusb.

Refs <https://github.com/mesonbuild/meson/issues/3878>
PR #3546 <https://github.com/Genymobile/scrcpy/pull/3546>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-23 12:31:44 +02:00
Romain Vimont
b62424a98a Build log.c for test_cli
On Windows, sc_log_windows_error() is called from net.c, so log.c must
also be compiled.

Fixes #3542 <https://github.com/Genymobile/scrcpy/issues/3542>
2022-10-19 15:17:43 +02:00
Romain Vimont
ffc7b91693 Add missing include <string.h> for strlen() 2022-10-19 15:14:56 +02:00
Romain Vimont
cb46e4a64a Add missing include <string.h> for memmove() 2022-10-19 15:13:55 +02:00
17 changed files with 590 additions and 60 deletions

View File

@@ -395,8 +395,8 @@ address), connect the device over USB, then run:
scrcpy --tcpip # without arguments
```
It will automatically find the device IP address, enable TCP/IP mode, then
connect to the device before starting.
It will automatically find the device IP address and adb port, enable TCP/IP
mode if necessary, then connect to the device before starting.
##### Manual

View File

@@ -23,6 +23,7 @@ src = [
'src/options.c',
'src/receiver.c',
'src/recorder.c',
'src/rtp.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
@@ -267,6 +268,7 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/log.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',

View File

@@ -275,7 +275,7 @@ Configure and reconnect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting.
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
.TP
.B \-S, \-\-turn\-screen\-off

View File

@@ -12,7 +12,7 @@
#include "util/net.h"
#include "util/thread.h"
#define SC_DEMUXER_MAX_SINKS 2
#define SC_DEMUXER_MAX_SINKS 3
struct sc_demuxer {
sc_socket socket;

View File

@@ -4,6 +4,10 @@
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#ifdef _WIN32
#include <windows.h>
#include "util/str.h"
#endif
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
@@ -18,8 +22,8 @@
#include "version.h"
int
main(int argc, char *argv[]) {
#ifdef __WINDOWS__
main_scrcpy(int argc, char *argv[]) {
#ifdef _WIN32
// disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
setbuf(stdout, NULL);
@@ -80,3 +84,52 @@ main(int argc, char *argv[]) {
return ret;
}
int
main(int argc, char *argv[]) {
#ifndef _WIN32
return main_scrcpy(argc, argv);
#else
(void) argc;
(void) argv;
int wargc;
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
if (!wargv) {
LOG_OOM();
return SCRCPY_EXIT_FAILURE;
}
char **argv_utf8 = malloc((wargc + 1) * sizeof(*argv_utf8));
if (!argv_utf8) {
LOG_OOM();
LocalFree(wargv);
return SCRCPY_EXIT_FAILURE;
}
argv_utf8[wargc] = NULL;
for (int i = 0; i < wargc; ++i) {
argv_utf8[i] = sc_str_from_wchars(wargv[i]);
if (!argv_utf8[i]) {
LOG_OOM();
for (int j = 0; j < i; ++j) {
free(argv_utf8[j]);
}
LocalFree(wargv);
free(argv_utf8);
return SCRCPY_EXIT_FAILURE;
}
}
LocalFree(wargv);
int ret = main_scrcpy(wargc, argv_utf8);
for (int i = 0; i < wargc; ++i) {
free(argv_utf8[i]);
}
free(argv_utf8);
return ret;
#endif
}

319
app/src/rtp.c Normal file
View File

@@ -0,0 +1,319 @@
#include "rtp.h"
#include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include "util/log.h"
/** Downcast packet_sink to rtp */
#define DOWNCAST(SINK) container_of(SINK, struct sc_rtp, packet_sink)
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
static struct sc_rtp_packet *
sc_rtp_packet_new(const AVPacket *packet) {
struct sc_rtp_packet *rtp = malloc(sizeof(*rtp));
if (!rtp) {
LOG_OOM();
return NULL;
}
rtp->packet = av_packet_alloc();
if (!rtp->packet) {
LOG_OOM();
free(rtp);
return NULL;
}
if (av_packet_ref(rtp->packet, packet)) {
av_packet_free(&rtp->packet);
free(rtp);
return NULL;
}
return rtp;
}
static void
sc_rtp_packet_delete(struct sc_rtp_packet *rtp) {
av_packet_free(&rtp->packet);
free(rtp);
}
static void
sc_rtp_queue_clear(struct sc_rtp_queue *queue) {
while (!sc_queue_is_empty(queue)) {
struct sc_rtp_packet *rtp;
sc_queue_take(queue, next, &rtp);
sc_rtp_packet_delete(rtp);
}
}
static bool
sc_rtp_write_header(struct sc_rtp *rtp, const AVPacket *packet) {
AVStream *ostream = rtp->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOG_OOM();
return false;
}
// copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size);
ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size;
int ret = avformat_write_header(rtp->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write RTP header");
return false;
}
return true;
}
static void
sc_rtp_rescale_packet(struct sc_rtp *rtp, AVPacket *packet) {
AVStream *ostream = rtp->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
static bool
sc_rtp_write(struct sc_rtp *rtp, AVPacket *packet) {
if (!rtp->header_written) {
if (packet->pts != AV_NOPTS_VALUE) {
LOGE("The first packet is not a config packet");
return false;
}
bool ok = sc_rtp_write_header(rtp, packet);
if (!ok) {
return false;
}
rtp->header_written = true;
return true;
}
if (packet->pts == AV_NOPTS_VALUE) {
// ignore config packets
return true;
}
sc_rtp_rescale_packet(rtp, packet);
return av_write_frame(rtp->ctx, packet) >= 0;
}
static int
run_rtp(void *data) {
struct sc_rtp *rtp = data;
for (;;) {
sc_mutex_lock(&rtp->mutex);
while (!rtp->stopped && sc_queue_is_empty(&rtp->queue)) {
sc_cond_wait(&rtp->queue_cond, &rtp->mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the streaming) before actually stopping
if (rtp->stopped && sc_queue_is_empty(&rtp->queue)) {
sc_mutex_unlock(&rtp->mutex);
break;
}
struct sc_rtp_packet *pkt;
sc_queue_take(&rtp->queue, next, &pkt);
sc_mutex_unlock(&rtp->mutex);
bool ok = sc_rtp_write(rtp, pkt->packet);
sc_rtp_packet_delete(pkt);
if (!ok) {
LOGE("Could not send packet");
sc_mutex_lock(&rtp->mutex);
rtp->failed = true;
// discard pending packets
sc_rtp_queue_clear(&rtp->queue);
sc_mutex_unlock(&rtp->mutex);
break;
}
}
if (!rtp->failed) {
if (rtp->header_written) {
int ret = av_write_trailer(rtp->ctx);
if (ret < 0) {
LOGE("Failed to write RTP trailer");
rtp->failed = true;
}
} else {
// nothing has been sent
rtp->failed = true;
}
}
if (rtp->failed) {
LOGE("Streaming over RTP failed");
} else {
LOGI("Streaming over RTP complete");
}
LOGD("RTP streaming thread ended");
return 0;
}
static bool
sc_rtp_open(struct sc_rtp *rtp, const AVCodec *input_codec) {
bool ok = sc_mutex_init(&rtp->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&rtp->queue_cond);
if (!ok) {
goto error_mutex_destroy;
}
sc_queue_init(&rtp->queue);
rtp->stopped = false;
rtp->failed = false;
rtp->header_written = false;
int ret = avformat_alloc_output_context2(&rtp->ctx, NULL, "rtp",
rtp->out_url);
if (ret < 0) {
goto error_cond_destroy;
}
AVStream *ostream = avformat_new_stream(rtp->ctx, input_codec);
if (!ostream) {
goto error_avformat_free_context;
}
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->width = rtp->declared_frame_size.width;
ostream->codecpar->height = rtp->declared_frame_size.height;
ret = avio_open(&rtp->ctx->pb, rtp->out_url, AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output: %s", rtp->out_url);
// ostream will be cleaned up during context cleaning
goto error_avformat_free_context;
}
LOGD("Starting RTP thread");
ok = sc_thread_create(&rtp->thread, run_rtp, "scrcpy-rtp", rtp);
if (!ok) {
LOGE("Could not start RTP thread");
goto error_avio_close;
}
LOGI("Streaming started to %s", rtp->out_url);
return true;
error_avio_close:
avio_close(rtp->ctx->pb);
error_avformat_free_context:
avformat_free_context(rtp->ctx);
error_cond_destroy:
sc_cond_destroy(&rtp->queue_cond);
error_mutex_destroy:
sc_mutex_destroy(&rtp->mutex);
return false;
}
static void
sc_rtp_close(struct sc_rtp *rtp) {
sc_mutex_lock(&rtp->mutex);
rtp->stopped = true;
sc_cond_signal(&rtp->queue_cond);
sc_mutex_unlock(&rtp->mutex);
sc_thread_join(&rtp->thread, NULL);
avio_close(rtp->ctx->pb);
avformat_free_context(rtp->ctx);
sc_cond_destroy(&rtp->queue_cond);
sc_mutex_destroy(&rtp->mutex);
}
static bool
sc_rtp_push(struct sc_rtp *rtp, const AVPacket *packet) {
sc_mutex_lock(&rtp->mutex);
assert(!rtp->stopped);
if (rtp->failed) {
// reject any new packet (this will stop the stream)
sc_mutex_unlock(&rtp->mutex);
return false;
}
struct sc_rtp_packet *pkt = sc_rtp_packet_new(packet);
if (!pkt) {
LOG_OOM();
sc_mutex_unlock(&rtp->mutex);
return false;
}
sc_queue_push(&rtp->queue, next, pkt);
sc_cond_signal(&rtp->queue_cond);
sc_mutex_unlock(&rtp->mutex);
return true;
}
static bool
sc_rtp_packet_sink_open(struct sc_packet_sink *sink,
const AVCodec *codec) {
struct sc_rtp *rtp = DOWNCAST(sink);
return sc_rtp_open(rtp, codec);
}
static void
sc_rtp_packet_sink_close(struct sc_packet_sink *sink) {
struct sc_rtp *rtp = DOWNCAST(sink);
sc_rtp_close(rtp);
}
static bool
sc_rtp_packet_sink_push(struct sc_packet_sink *sink,
const AVPacket *packet) {
struct sc_rtp *rtp = DOWNCAST(sink);
return sc_rtp_push(rtp, packet);
}
bool
sc_rtp_init(struct sc_rtp *rtp, const char *out_url,
struct sc_size declared_frame_size) {
rtp->out_url = strdup(out_url);
if (!rtp->out_url) {
LOG_OOM();
return false;
}
rtp->declared_frame_size = declared_frame_size;
static const struct sc_packet_sink_ops ops = {
.open = sc_rtp_packet_sink_open,
.close = sc_rtp_packet_sink_close,
.push = sc_rtp_packet_sink_push,
};
rtp->packet_sink.ops = &ops;
return true;
}
void
sc_rtp_destroy(struct sc_rtp *rtp) {
free(rtp->out_url);
}

44
app/src/rtp.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef SC_RTP_H
#define SC_RTP_H
#include "common.h"
#include <stdbool.h>
#include <libavformat/avformat.h>
#include "coords.h"
#include "trait/packet_sink.h"
#include "util/queue.h"
#include "util/thread.h"
struct sc_rtp_packet {
AVPacket *packet;
struct sc_rtp_packet *next;
};
struct sc_rtp_queue SC_QUEUE(struct sc_rtp_packet);
struct sc_rtp {
struct sc_packet_sink packet_sink; // packet sink trait;
char *out_url;
AVFormatContext *ctx;
struct sc_size declared_frame_size;
bool header_written;
sc_thread thread;
sc_mutex mutex;
sc_cond queue_cond;
bool stopped; // set on rtp_close()
bool failed; // set on packet write failure
struct sc_rtp_queue queue;
};
bool
sc_rtp_init(struct sc_rtp *rtp, const char *out_url,
struct sc_size declared_frame_size);
void
sc_rtp_destroy(struct sc_rtp *rtp);
#endif

View File

@@ -21,6 +21,7 @@
#include "keyboard_inject.h"
#include "mouse_inject.h"
#include "recorder.h"
#include "rtp.h"
#include "screen.h"
#include "server.h"
#ifdef HAVE_USB
@@ -42,6 +43,7 @@ struct scrcpy {
struct sc_demuxer demuxer;
struct sc_decoder decoder;
struct sc_recorder recorder;
struct sc_rtp rtp;
#ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink;
#endif
@@ -283,6 +285,7 @@ scrcpy(struct scrcpy_options *options) {
bool server_started = false;
bool file_pusher_initialized = false;
bool recorder_initialized = false;
bool rtp_initialized = false;
#ifdef HAVE_V4L2
bool v4l2_sink_initialized = false;
#endif
@@ -420,6 +423,14 @@ scrcpy(struct scrcpy_options *options) {
sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink);
}
struct sc_rtp *rtp = NULL;
if (!sc_rtp_init(&s->rtp, "rtp://127.0.0.1:1234", info->frame_size)) {
goto end;
}
rtp = &s->rtp;
rtp_initialized = true;
sc_demuxer_add_sink(&s->demuxer, &rtp->packet_sink);
struct sc_controller *controller = NULL;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
@@ -707,6 +718,10 @@ end:
sc_controller_destroy(&s->controller);
}
if (rtp_initialized) {
sc_rtp_destroy(&s->rtp);
}
if (recorder_initialized) {
sc_recorder_destroy(&s->recorder);
}

View File

@@ -19,6 +19,8 @@
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
#define SC_ADB_PORT_DEFAULT 5555
static char *
get_server_path(void) {
#ifdef __WINDOWS__
@@ -513,27 +515,36 @@ sc_server_on_terminated(void *userdata) {
LOGD("Server terminated");
}
static bool
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
static uint16_t
get_adb_tcp_port(struct sc_server *server, const char *serial) {
struct sc_intr *intr = &server->intr;
char *current_port =
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
if (!current_port) {
return false;
return 0;
}
// Is the device is listening on TCP on port 5555?
bool enabled = !strcmp("5555", current_port);
long value;
bool ok = sc_str_parse_integer(current_port, &value);
free(current_port);
return enabled;
if (!ok) {
return 0;
}
if (value < 0 || value > 0xFFFF) {
return 0;
}
return value;
}
static bool
wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
unsigned attempts, sc_tick delay) {
if (is_tcpip_mode_enabled(server, serial)) {
LOGI("TCP/IP mode enabled");
uint16_t expected_port, unsigned attempts,
sc_tick delay) {
uint16_t adb_port = get_adb_tcp_port(server, serial);
if (adb_port == expected_port) {
return true;
}
@@ -547,28 +558,23 @@ wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
return false;
}
if (is_tcpip_mode_enabled(server, serial)) {
LOGI("TCP/IP mode enabled");
adb_port = get_adb_tcp_port(server, serial);
if (adb_port == expected_port) {
return true;
}
} while (--attempts);
return false;
}
char *
append_port_5555(const char *ip) {
size_t len = strlen(ip);
// sizeof counts the final '\0'
char *ip_port = malloc(len + sizeof(":5555"));
if (!ip_port) {
static char *
append_port(const char *ip, uint16_t port) {
char *ip_port;
int ret = asprintf(&ip_port, "%s:%" PRIu16, ip, port);
if (ret == -1) {
LOG_OOM();
return NULL;
}
memcpy(ip_port, ip, len);
memcpy(ip_port + len, ":5555", sizeof(":5555"));
return ip_port;
}
@@ -586,34 +592,36 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
return NULL;
}
char *ip_port = append_port_5555(ip);
free(ip);
if (!ip_port) {
return NULL;
}
uint16_t adb_port = get_adb_tcp_port(server, serial);
if (adb_port) {
LOGI("TCP/IP mode already enabled on port %" PRIu16, adb_port);
} else {
LOGI("Enabling TCP/IP mode on port " SC_STR(SC_ADB_PORT_DEFAULT) "...");
bool tcp_mode = is_tcpip_mode_enabled(server, serial);
if (!tcp_mode) {
bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT);
bool ok = sc_adb_tcpip(intr, serial, SC_ADB_PORT_DEFAULT,
SC_ADB_NO_STDOUT);
if (!ok) {
LOGE("Could not restart adbd in TCP/IP mode");
goto error;
free(ip);
return NULL;
}
unsigned attempts = 40;
sc_tick delay = SC_TICK_FROM_MS(250);
ok = wait_tcpip_mode_enabled(server, serial, attempts, delay);
ok = wait_tcpip_mode_enabled(server, serial, SC_ADB_PORT_DEFAULT,
attempts, delay);
if (!ok) {
goto error;
free(ip);
return NULL;
}
adb_port = SC_ADB_PORT_DEFAULT;
LOGI("TCP/IP mode enabled on port " SC_STR(SC_ADB_PORT_DEFAULT));
}
char *ip_port = append_port(ip, adb_port);
free(ip);
return ip_port;
error:
free(ip_port);
return NULL;
}
static bool
@@ -640,7 +648,8 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
const char *addr) {
// Append ":5555" if no port is present
bool contains_port = strchr(addr, ':');
char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr);
char *ip_port = contains_port ? strdup(addr)
: append_port(addr, SC_ADB_PORT_DEFAULT);
if (!ip_port) {
LOG_OOM();
return false;

View File

@@ -6,6 +6,10 @@
#include <stdbool.h>
#include <stddef.h>
/* Stringify a numeric value */
#define SC_STR(s) SC_XSTR(s)
#define SC_XSTR(s) #s
/**
* Like strncpy(), except:
* - it copies at most n-1 chars

View File

@@ -1,6 +1,7 @@
#include "thread.h"
#include <assert.h>
#include <string.h>
#include <SDL2/SDL_thread.h>
#include "log.h"

View File

@@ -6,6 +6,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
// Adapted from vlc_vector:
// <https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h>

View File

@@ -22,4 +22,4 @@ ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1'
prebuilt_sdl2 = 'SDL2-2.0.22/i686-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.26'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-Win32'
prebuilt_libusb = 'libusb-1.0.26/MinGW-Win32'

View File

@@ -22,4 +22,4 @@ ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1'
prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.26'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64'
prebuilt_libusb = 'libusb-1.0.26/MinGW-x64'

View File

@@ -15,6 +15,9 @@ public class ClipboardManager {
private Method getPrimaryClipMethod;
private Method setPrimaryClipMethod;
private Method addPrimaryClipChangedListener;
private boolean alternativeGetMethod;
private boolean alternativeSetMethod;
private boolean alternativeAddListenerMethod;
public ClipboardManager(IInterface manager) {
this.manager = manager;
@@ -25,7 +28,12 @@ public class ClipboardManager {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
} else {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
} catch (NoSuchMethodException e) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class);
alternativeGetMethod = true;
}
}
}
return getPrimaryClipMethod;
@@ -36,23 +44,34 @@ public class ClipboardManager {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
} else {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
try {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
} catch (NoSuchMethodException e) {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class);
alternativeSetMethod = true;
}
}
}
return setPrimaryClipMethod;
}
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException {
private static ClipData getPrimaryClip(Method method, boolean alternativeMethod, IInterface manager)
throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME);
}
if (alternativeMethod) {
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID);
}
return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
}
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData)
private static void setPrimaryClip(Method method, boolean alternativeMethod, IInterface manager, ClipData clipData)
throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME);
} else if (alternativeMethod) {
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID);
} else {
method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
}
@@ -61,7 +80,7 @@ public class ClipboardManager {
public CharSequence getText() {
try {
Method method = getGetPrimaryClipMethod();
ClipData clipData = getPrimaryClip(method, manager);
ClipData clipData = getPrimaryClip(method, alternativeGetMethod, manager);
if (clipData == null || clipData.getItemCount() == 0) {
return null;
}
@@ -76,7 +95,7 @@ public class ClipboardManager {
try {
Method method = getSetPrimaryClipMethod();
ClipData clipData = ClipData.newPlainText(null, text);
setPrimaryClip(method, manager, clipData);
setPrimaryClip(method, alternativeSetMethod, manager, clipData);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);
@@ -84,13 +103,14 @@ public class ClipboardManager {
}
}
private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener)
throws InvocationTargetException, IllegalAccessException {
private static void addPrimaryClipChangedListener(Method method, boolean alternativeMethod, IInterface manager,
IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME);
} else {
} else if (alternativeMethod) {
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID);
} else
method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID);
}
}
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
@@ -99,8 +119,14 @@ public class ClipboardManager {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
} else {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
try {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
} catch (NoSuchMethodException e) {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, int.class);
alternativeAddListenerMethod = true;
}
}
}
return addPrimaryClipChangedListener;
@@ -109,7 +135,7 @@ public class ClipboardManager {
public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
try {
Method method = getAddPrimaryClipChangedListener();
addPrimaryClipChangedListener(method, manager, listener);
addPrimaryClipChangedListener(method, alternativeAddListenerMethod, manager, listener);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);

View File

@@ -21,7 +21,7 @@ public final class DisplayManager {
// public to call it from unit tests
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
Pattern regex = Pattern.compile(
"^ mOverrideDisplayInfo=DisplayInfo\\{\".*?\", displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, "
"^ mOverrideDisplayInfo=DisplayInfo\\{\".*?, displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, "
+ "rotation ([0-9]+).*?, layerStack ([0-9]+)",
Pattern.MULTILINE);
Matcher m = regex.matcher(dumpsysDisplayOutput);

View File

@@ -183,4 +183,60 @@ public class CommandParserTest {
Assert.assertEquals(1080, displayInfo.getSize().getWidth());
Assert.assertEquals(2280, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayAPI29WithNoFlags() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=2\n"
+ " Display 0:\n"
+ " mDisplayId=0\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mAllowedDisplayModes=[1]\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, "
+ "real 3664 x 1920, largest app 3664 x 1920, smallest app 3664 x 1920, mode 61, defaultMode 61, modes ["
+ "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, "
+ "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, "
+ "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, "
+ "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, "
+ "real 3664 x 1920, largest app 3664 x 3620, smallest app 1920 x 1876, mode 61, defaultMode 61, modes ["
+ "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, "
+ "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, "
+ "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, "
+ "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n"
+ " Display 31:\n"
+ " mDisplayId=31\n"
+ " mLayerStack=31\n"
+ " mHasContent=true\n"
+ " mAllowedDisplayModes=[92]\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=PanelLayer-#main\n"
+ " mBaseDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId "
+ "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 110, smallest app 800 x "
+ "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, "
+ "type VIRTUAL, state ON, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId "
+ "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 800, smallest app 110 x "
+ "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, "
+ "type VIRTUAL, state OFF, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 31);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(31, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(31, displayInfo.getLayerStack());
Assert.assertEquals(0, displayInfo.getFlags());
Assert.assertEquals(800, displayInfo.getSize().getWidth());
Assert.assertEquals(110, displayInfo.getSize().getHeight());
}
}