Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d298ad577 | ||
|
|
0e34f4fbf7 | ||
|
|
b13aece7a1 | ||
|
|
bd1deffa70 | ||
|
|
6469b55861 | ||
|
|
597703b62e | ||
|
|
48bb6f2ea8 | ||
|
|
d71587e39b | ||
|
|
b62424a98a | ||
|
|
ffc7b91693 | ||
|
|
cb46e4a64a | ||
|
|
16e2c1ce26 | ||
|
|
1bfbadef96 | ||
|
|
40644994e8 | ||
|
|
7505f7117e | ||
|
|
949b64dff2 | ||
|
|
00e9e69c2a | ||
|
|
4a5cdcd390 |
@@ -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
|
||||
|
||||
|
||||
@@ -93,6 +93,11 @@ _scrcpy() {
|
||||
COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
-s|--serial)
|
||||
# Use 'adb devices' to list serial numbers
|
||||
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
|
||||
return
|
||||
;;
|
||||
-b|--bitrate \
|
||||
|--codec-options \
|
||||
|--crop \
|
||||
@@ -103,7 +108,6 @@ _scrcpy() {
|
||||
|-m|--max-size \
|
||||
|-p|--port \
|
||||
|--push-target \
|
||||
|-s|--serial \
|
||||
|--tunnel-host \
|
||||
|--tunnel-port \
|
||||
|--v4l2-buffer \
|
||||
|
||||
@@ -47,7 +47,7 @@ arguments=(
|
||||
'--record-format=[Force recording format]:format:(mp4 mkv)'
|
||||
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
|
||||
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
|
||||
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]'
|
||||
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
||||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
||||
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
||||
{-t,--show-touches}'[Show physical touches]'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,7 +37,7 @@ static const char *const android_motionevent_action_labels[] = {
|
||||
"move",
|
||||
"cancel",
|
||||
"outside",
|
||||
"ponter-down",
|
||||
"pointer-down",
|
||||
"pointer-up",
|
||||
"hover-move",
|
||||
"scroll",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
319
app/src/rtp.c
Normal 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
44
app/src/rtp.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -92,8 +92,14 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
||||
close(in[0]);
|
||||
}
|
||||
close(in[1]);
|
||||
} else {
|
||||
int devnull = open("/dev/null", O_RDONLY | O_CREAT, 0666);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDIN_FILENO);
|
||||
} else {
|
||||
LOGE("Could not open /dev/null for stdin");
|
||||
}
|
||||
}
|
||||
// Do not close stdin in the child process, this makes adb fail on Linux
|
||||
|
||||
if (pout) {
|
||||
if (out[1] != STDOUT_FILENO) {
|
||||
@@ -102,8 +108,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
||||
}
|
||||
close(out[0]);
|
||||
} else if (!inherit_stdout) {
|
||||
// Close stdout in the child process
|
||||
close(STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
} else {
|
||||
LOGE("Could not open /dev/null for stdout");
|
||||
}
|
||||
}
|
||||
|
||||
if (perr) {
|
||||
@@ -113,8 +123,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
||||
}
|
||||
close(err[0]);
|
||||
} else if (!inherit_stderr) {
|
||||
// Close stderr in the child process
|
||||
close(STDERR_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
} else {
|
||||
LOGE("Could not open /dev/null for stderr");
|
||||
}
|
||||
}
|
||||
|
||||
close(internal[0]);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "thread.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL_thread.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -16,6 +16,7 @@ SCRCPY_VERSION_NAME=1.24
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-33}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
|
||||
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
|
||||
|
||||
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||
CLASSES_DIR="$BUILD_DIR/classes"
|
||||
@@ -41,9 +42,8 @@ EOF
|
||||
|
||||
echo "Generating java from aidl..."
|
||||
cd "$SERVER_DIR/src/main/aidl"
|
||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
||||
android/view/IRotationWatcher.aidl
|
||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \
|
||||
android/content/IOnPrimaryClipChangedListener.aidl
|
||||
|
||||
echo "Compiling java sources..."
|
||||
@@ -59,8 +59,7 @@ cd "$CLASSES_DIR"
|
||||
if [[ $PLATFORM -lt 31 ]]
|
||||
then
|
||||
# use dx
|
||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
||||
--output "$BUILD_DIR/classes.dex" \
|
||||
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
|
||||
android/view/*.class \
|
||||
android/content/*.class \
|
||||
com/genymobile/scrcpy/*.class \
|
||||
@@ -72,7 +71,7 @@ then
|
||||
rm -rf classes.dex classes
|
||||
else
|
||||
# use d8
|
||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
|
||||
"$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \
|
||||
--output "$BUILD_DIR/classes.zip" \
|
||||
android/view/*.class \
|
||||
android/content/*.class \
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Base64;
|
||||
@@ -164,12 +162,10 @@ public final class CleanUp {
|
||||
Config config = Config.fromBase64(args[0]);
|
||||
|
||||
if (config.disableShowTouches || config.restoreStayOn != -1) {
|
||||
ServiceManager serviceManager = new ServiceManager();
|
||||
Settings settings = new Settings(serviceManager);
|
||||
if (config.disableShowTouches) {
|
||||
Ln.i("Disabling \"show touches\"");
|
||||
try {
|
||||
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
|
||||
Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not restore \"show_touches\"", e);
|
||||
}
|
||||
@@ -177,7 +173,7 @@ public final class CleanUp {
|
||||
if (config.restoreStayOn != -1) {
|
||||
Ln.i("Restoring \"stay awake\"");
|
||||
try {
|
||||
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
|
||||
Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,6 @@ public final class Device {
|
||||
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
||||
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
|
||||
|
||||
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
|
||||
private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);
|
||||
|
||||
public interface RotationListener {
|
||||
void onRotationChanged(int rotation);
|
||||
}
|
||||
@@ -66,9 +63,9 @@ public final class Device {
|
||||
|
||||
public Device(Options options) {
|
||||
displayId = options.getDisplayId();
|
||||
DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId);
|
||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||
if (displayInfo == null) {
|
||||
int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds();
|
||||
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
|
||||
throw new InvalidDisplayIdException(displayId, displayIds);
|
||||
}
|
||||
|
||||
@@ -82,7 +79,7 @@ public final class Device {
|
||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
|
||||
layerStack = displayInfo.getLayerStack();
|
||||
|
||||
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||
ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||
@Override
|
||||
public void onRotationChanged(int rotation) {
|
||||
synchronized (Device.this) {
|
||||
@@ -98,7 +95,7 @@ public final class Device {
|
||||
|
||||
if (options.getControl() && options.getClipboardAutosync()) {
|
||||
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically
|
||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
||||
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
|
||||
if (clipboardManager != null) {
|
||||
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||
@Override
|
||||
@@ -192,7 +189,7 @@ public final class Device {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode);
|
||||
return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode);
|
||||
}
|
||||
|
||||
public boolean injectEvent(InputEvent event, int injectMode) {
|
||||
@@ -220,7 +217,7 @@ public final class Device {
|
||||
}
|
||||
|
||||
public static boolean isScreenOn() {
|
||||
return SERVICE_MANAGER.getPowerManager().isScreenOn();
|
||||
return ServiceManager.getPowerManager().isScreenOn();
|
||||
}
|
||||
|
||||
public synchronized void setRotationListener(RotationListener rotationListener) {
|
||||
@@ -232,19 +229,19 @@ public final class Device {
|
||||
}
|
||||
|
||||
public static void expandNotificationPanel() {
|
||||
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
|
||||
ServiceManager.getStatusBarManager().expandNotificationsPanel();
|
||||
}
|
||||
|
||||
public static void expandSettingsPanel() {
|
||||
SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
|
||||
ServiceManager.getStatusBarManager().expandSettingsPanel();
|
||||
}
|
||||
|
||||
public static void collapsePanels() {
|
||||
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
|
||||
ServiceManager.getStatusBarManager().collapsePanels();
|
||||
}
|
||||
|
||||
public static String getClipboardText() {
|
||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
||||
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
|
||||
if (clipboardManager == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -256,7 +253,7 @@ public final class Device {
|
||||
}
|
||||
|
||||
public boolean setClipboardText(String text) {
|
||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
||||
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
|
||||
if (clipboardManager == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -299,7 +296,7 @@ public final class Device {
|
||||
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||
*/
|
||||
public static void rotateDevice() {
|
||||
WindowManager wm = SERVICE_MANAGER.getWindowManager();
|
||||
WindowManager wm = ServiceManager.getWindowManager();
|
||||
|
||||
boolean accelerometerRotation = !wm.isRotationFrozen();
|
||||
|
||||
@@ -315,8 +312,4 @@ public final class Device {
|
||||
wm.thawRotation();
|
||||
}
|
||||
}
|
||||
|
||||
public static Settings getSettings() {
|
||||
return SETTINGS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,9 @@ public final class Server {
|
||||
int restoreStayOn = -1;
|
||||
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
|
||||
if (options.getShowTouches() || options.getStayAwake()) {
|
||||
Settings settings = Device.getSettings();
|
||||
if (options.getShowTouches()) {
|
||||
try {
|
||||
String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
||||
// If "show touches" was disabled, it must be disabled back on clean up
|
||||
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
|
||||
} catch (SettingsException e) {
|
||||
@@ -34,7 +33,7 @@ public final class Server {
|
||||
if (options.getStayAwake()) {
|
||||
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||
try {
|
||||
String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
||||
try {
|
||||
restoreStayOn = Integer.parseInt(oldValue);
|
||||
if (restoreStayOn == stayOn) {
|
||||
|
||||
@@ -7,16 +7,14 @@ import android.os.Build;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Settings {
|
||||
public final class Settings {
|
||||
|
||||
public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM;
|
||||
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
|
||||
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
|
||||
|
||||
private final ServiceManager serviceManager;
|
||||
|
||||
public Settings(ServiceManager serviceManager) {
|
||||
this.serviceManager = serviceManager;
|
||||
private Settings() {
|
||||
/* not instantiable */
|
||||
}
|
||||
|
||||
private static void execSettingsPut(String table, String key, String value) throws SettingsException {
|
||||
@@ -35,10 +33,10 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
public 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) {
|
||||
// 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);
|
||||
} catch (SettingsException e) {
|
||||
Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e);
|
||||
@@ -48,10 +46,10 @@ public class Settings {
|
||||
return execSettingsGet(table, key);
|
||||
}
|
||||
|
||||
public 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) {
|
||||
// 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);
|
||||
} catch (SettingsException e) {
|
||||
Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e);
|
||||
@@ -61,10 +59,10 @@ public class Settings {
|
||||
execSettingsPut(table, key, value);
|
||||
}
|
||||
|
||||
public 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) {
|
||||
// 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);
|
||||
if (!value.equals(oldValue)) {
|
||||
provider.putValue(table, key, value);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -21,8 +21,8 @@ public final class DisplayManager {
|
||||
// public to call it from unit tests
|
||||
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
|
||||
Pattern regex = Pattern.compile(
|
||||
"^ mBaseDisplayInfo=DisplayInfo\\{\".*?\", displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, rotation "
|
||||
+ "([0-9]+).*?, layerStack ([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);
|
||||
if (!m.find()) {
|
||||
|
||||
@@ -13,27 +13,30 @@ public final class ServiceManager {
|
||||
public static final String PACKAGE_NAME = "com.android.shell";
|
||||
public static final int USER_ID = 0;
|
||||
|
||||
private final Method getServiceMethod;
|
||||
|
||||
private WindowManager windowManager;
|
||||
private DisplayManager displayManager;
|
||||
private InputManager inputManager;
|
||||
private PowerManager powerManager;
|
||||
private StatusBarManager statusBarManager;
|
||||
private ClipboardManager clipboardManager;
|
||||
private ActivityManager activityManager;
|
||||
|
||||
public ServiceManager() {
|
||||
private static final Method GET_SERVICE_METHOD;
|
||||
static {
|
||||
try {
|
||||
getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
|
||||
GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private IInterface getService(String service, String type) {
|
||||
private static WindowManager windowManager;
|
||||
private static DisplayManager displayManager;
|
||||
private static InputManager inputManager;
|
||||
private static PowerManager powerManager;
|
||||
private static StatusBarManager statusBarManager;
|
||||
private static ClipboardManager clipboardManager;
|
||||
private static ActivityManager activityManager;
|
||||
|
||||
private ServiceManager() {
|
||||
/* not instantiable */
|
||||
}
|
||||
|
||||
private static IInterface getService(String service, String type) {
|
||||
try {
|
||||
IBinder binder = (IBinder) getServiceMethod.invoke(null, service);
|
||||
IBinder binder = (IBinder) GET_SERVICE_METHOD.invoke(null, service);
|
||||
Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class);
|
||||
return (IInterface) asInterfaceMethod.invoke(null, binder);
|
||||
} catch (Exception e) {
|
||||
@@ -41,14 +44,14 @@ public final class ServiceManager {
|
||||
}
|
||||
}
|
||||
|
||||
public WindowManager getWindowManager() {
|
||||
public static WindowManager getWindowManager() {
|
||||
if (windowManager == null) {
|
||||
windowManager = new WindowManager(getService("window", "android.view.IWindowManager"));
|
||||
}
|
||||
return windowManager;
|
||||
}
|
||||
|
||||
public DisplayManager getDisplayManager() {
|
||||
public static DisplayManager getDisplayManager() {
|
||||
if (displayManager == null) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
|
||||
@@ -62,7 +65,7 @@ public final class ServiceManager {
|
||||
return displayManager;
|
||||
}
|
||||
|
||||
public InputManager getInputManager() {
|
||||
public static InputManager getInputManager() {
|
||||
if (inputManager == null) {
|
||||
try {
|
||||
Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
|
||||
@@ -75,21 +78,21 @@ public final class ServiceManager {
|
||||
return inputManager;
|
||||
}
|
||||
|
||||
public PowerManager getPowerManager() {
|
||||
public static PowerManager getPowerManager() {
|
||||
if (powerManager == null) {
|
||||
powerManager = new PowerManager(getService("power", "android.os.IPowerManager"));
|
||||
}
|
||||
return powerManager;
|
||||
}
|
||||
|
||||
public StatusBarManager getStatusBarManager() {
|
||||
public static StatusBarManager getStatusBarManager() {
|
||||
if (statusBarManager == null) {
|
||||
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
|
||||
}
|
||||
return statusBarManager;
|
||||
}
|
||||
|
||||
public ClipboardManager getClipboardManager() {
|
||||
public static ClipboardManager getClipboardManager() {
|
||||
if (clipboardManager == null) {
|
||||
IInterface clipboard = getService("clipboard", "android.content.IClipboard");
|
||||
if (clipboard == null) {
|
||||
@@ -103,7 +106,7 @@ public final class ServiceManager {
|
||||
return clipboardManager;
|
||||
}
|
||||
|
||||
public ActivityManager getActivityManager() {
|
||||
public static ActivityManager getActivityManager() {
|
||||
if (activityManager == null) {
|
||||
try {
|
||||
// On old Android versions, the ActivityManager is not exposed via AIDL,
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
|
||||
import com.genymobile.scrcpy.wrappers.DisplayManager;
|
||||
|
||||
import android.view.Display;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -50,6 +49,48 @@ public class CommandParserTest {
|
||||
Assert.assertEquals(3120, displayInfo.getSize().getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDisplayInfoFromDumpsysDisplayWithRotation() {
|
||||
/* @formatter:off */
|
||||
String partialOutput = "Logical Displays: size=1\n"
|
||||
+ " Display 0:\n"
|
||||
+ "mDisplayId=0\n"
|
||||
+ " mLayerStack=0\n"
|
||||
+ " mHasContent=true\n"
|
||||
+ " mDesiredDisplayModeSpecs={baseModeId=2 primaryRefreshRateRange=[90 90] appRequestRefreshRateRange=[90 90]}\n"
|
||||
+ " mRequestedColorMode=0\n"
|
||||
+ " mDisplayOffset=(0, 0)\n"
|
||||
+ " mDisplayScalingDisabled=false\n"
|
||||
+ " mPrimaryDisplayDevice=Built-in Screen\n"
|
||||
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, "
|
||||
+ "real 1440 x 3120, largest app 1440 x 3120, smallest app 1440 x 3120, appVsyncOff 2000000, presDeadline 11111111, mode 2, "
|
||||
+ "defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, width=1080, "
|
||||
+ "height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], "
|
||||
+ "mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, minimalPostProcessingSupported false, rotation 0, state ON, "
|
||||
+ "type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 (515.154 x 514.597) dpi, layerStack 0, colorMode 0, "
|
||||
+ "supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo DeviceProductInfo{name=, manufacturerPnpId=QCM, "
|
||||
+ "productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, relativeAddress=null}, removeMode 0}\n"
|
||||
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, "
|
||||
+ "FLAG_TRUSTED, real 3120 x 1440, largest app 3120 x 2983, smallest app 1440 x 1303, appVsyncOff 2000000, presDeadline 11111111, "
|
||||
+ "mode 2, defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, "
|
||||
+ "width=1080, height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities "
|
||||
+ "HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, "
|
||||
+ "minimalPostProcessingSupported false, rotation 3, state ON, type INTERNAL, uniqueId \"local:0\", app 3120 x 1440, density 600 "
|
||||
+ "(515.154 x 514.597) dpi, layerStack 0, colorMode 0, supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo "
|
||||
+ "DeviceProductInfo{name=, manufacturerPnpId=QCM, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, "
|
||||
+ "relativeAddress=null}, removeMode 0}\n"
|
||||
+ " mRequestedMinimalPostProcessing=false";
|
||||
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
|
||||
Assert.assertNotNull(displayInfo);
|
||||
Assert.assertEquals(0, displayInfo.getDisplayId());
|
||||
Assert.assertEquals(3, displayInfo.getRotation());
|
||||
Assert.assertEquals(0, displayInfo.getLayerStack());
|
||||
// FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported
|
||||
Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags());
|
||||
Assert.assertEquals(3120, displayInfo.getSize().getWidth());
|
||||
Assert.assertEquals(1440, displayInfo.getSize().getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDisplayInfoFromDumpsysDisplayAPI31() {
|
||||
/* @formatter:off */
|
||||
@@ -59,29 +100,30 @@ public class CommandParserTest {
|
||||
+ " mPhase=1\n"
|
||||
+ " mLayerStack=0\n"
|
||||
+ " mHasContent=true\n"
|
||||
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 Infinity]}\n"
|
||||
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 "
|
||||
+ "Infinity]}\n"
|
||||
+ " mRequestedColorMode=0\n"
|
||||
+ " mDisplayOffset=(0, 0)\n"
|
||||
+ " mDisplayScalingDisabled=false\n"
|
||||
+ " mPrimaryDisplayDevice=Built-in Screen\n"
|
||||
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS"
|
||||
+ ", FLAG_TRUSTED, real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff 1000000, presDeadline 16666666, "
|
||||
+ "mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, alternativeRefreshRates=[]}], "
|
||||
+ "hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, mMaxAverageLuminance=500.0, mMinLuminance=0.0}, "
|
||||
+ "userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state ON, type INTERNAL, uniqueId \"local:4619827259835644672\", "
|
||||
+ "app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, supportedColorModes [0], address {port=0, model=0x401cec6a7a2b7b}, "
|
||||
+ "deviceProductInfo DeviceProductInfo{name=EMU_display_0, manufacturerPnpId=GGL, productId=1, modelYear=null, "
|
||||
+ "manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, removeMode 0, refreshRateOverride 0.0, "
|
||||
+ "brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, "
|
||||
+ "FLAG_TRUSTED, real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff 1000000, presDeadline 16666666, mode 1, "
|
||||
+ "defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{"
|
||||
+ "mSupportedHdrTypes=[], mMaxLuminance=500.0, mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], "
|
||||
+ "minimalPostProcessingSupported false, rotation 0, state ON, type INTERNAL, uniqueId \"local:4619827259835644672\", app 1080 x 2148, "
|
||||
+ "density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, supportedColorModes [0], address {port=0, model=0x401cec6a7a2b7b}, "
|
||||
+ "deviceProductInfo DeviceProductInfo{name=EMU_display_0, manufacturerPnpId=GGL, productId=1, modelYear=null, "
|
||||
+ "manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, removeMode 0, refreshRateOverride 0.0, "
|
||||
+ "brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, "
|
||||
+ "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff "
|
||||
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
|
||||
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
|
||||
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
|
||||
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
|
||||
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
|
||||
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
|
||||
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, "
|
||||
+ "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff "
|
||||
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
|
||||
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
|
||||
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
|
||||
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
|
||||
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
|
||||
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
|
||||
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ " mRequestedMinimalPostProcessing=false\n"
|
||||
+ " mFrameRateOverrides=[]\n"
|
||||
+ " mPendingFrameRateOverrideUids={}\n";
|
||||
@@ -105,29 +147,30 @@ public class CommandParserTest {
|
||||
+ " mPhase=1\n"
|
||||
+ " mLayerStack=0\n"
|
||||
+ " mHasContent=true\n"
|
||||
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 Infinity]}\n"
|
||||
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 "
|
||||
+ "Infinity]}\n"
|
||||
+ " mRequestedColorMode=0\n"
|
||||
+ " mDisplayOffset=(0, 0)\n"
|
||||
+ " mDisplayScalingDisabled=false\n"
|
||||
+ " mPrimaryDisplayDevice=Built-in Screen\n"
|
||||
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, "
|
||||
+ "real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff 1000000, presDeadline 16666666, "
|
||||
+ "mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, alternativeRefreshRates=[]}], "
|
||||
+ "hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, mMaxAverageLuminance=500.0, mMinLuminance=0.0}, "
|
||||
+ "userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state ON, type INTERNAL, uniqueId \"local:4619827259835644672\", "
|
||||
+ "app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, supportedColorModes [0], address {port=0, model=0x401cec6a7a2b7b}, "
|
||||
+ "deviceProductInfo DeviceProductInfo{name=EMU_display_0, manufacturerPnpId=GGL, productId=1, modelYear=null, "
|
||||
+ "manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, removeMode 0, refreshRateOverride 0.0, "
|
||||
+ "brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, "
|
||||
+ "FLAG_TRUSTED, real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff 1000000, presDeadline 16666666, mode 1, "
|
||||
+ "defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{"
|
||||
+ "mSupportedHdrTypes=[], mMaxLuminance=500.0, mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], "
|
||||
+ "minimalPostProcessingSupported false, rotation 0, state ON, type INTERNAL, uniqueId \"local:4619827259835644672\", app 1080 x 2148, "
|
||||
+ "density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, supportedColorModes [0], address {port=0, model=0x401cec6a7a2b7b}, "
|
||||
+ "deviceProductInfo DeviceProductInfo{name=EMU_display_0, manufacturerPnpId=GGL, productId=1, modelYear=null, "
|
||||
+ "manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, removeMode 0, refreshRateOverride 0.0, "
|
||||
+ "brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ "real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff "
|
||||
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
|
||||
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
|
||||
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
|
||||
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
|
||||
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
|
||||
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
|
||||
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, "
|
||||
+ "real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff "
|
||||
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
|
||||
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
|
||||
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
|
||||
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
|
||||
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
|
||||
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
|
||||
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
|
||||
+ " mRequestedMinimalPostProcessing=false\n"
|
||||
+ " mFrameRateOverrides=[]\n"
|
||||
+ " mPendingFrameRateOverrideUids={}\n";
|
||||
@@ -140,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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user