Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c154d53d6 | ||
|
|
105b482082 | ||
|
|
4dbb7aa685 | ||
|
|
637683ed7a | ||
|
|
166257b808 | ||
|
|
3038bf4c3b | ||
|
|
b7af74b01a | ||
|
|
3f3e60dd7a | ||
|
|
d722b9de4d | ||
|
|
6a3d311d47 | ||
|
|
a1b97fbfaa | ||
|
|
5d5a1d6888 | ||
|
|
c19fefbcbd | ||
|
|
bc0f51023e | ||
|
|
3a8a7d5ba8 | ||
|
|
1fe2afb4df | ||
|
|
47d0c189ec | ||
|
|
c76264c149 | ||
|
|
be09e65fa4 | ||
|
|
de8a9a4045 | ||
|
|
4e47d78f30 | ||
|
|
0873b08c85 | ||
|
|
83c64d918b | ||
|
|
aedd90a955 | ||
|
|
e153f4657c | ||
|
|
dd2959b0cb | ||
|
|
6118c2b4cd | ||
|
|
fad8c68090 | ||
|
|
9b5b75d800 | ||
|
|
1270617422 | ||
|
|
0cbbf82917 | ||
|
|
25b3ad844c | ||
|
|
689314578e | ||
|
|
af61c7340d | ||
|
|
f0c991537f | ||
|
|
6b542c8ceb | ||
|
|
01b647b910 |
@@ -29,6 +29,7 @@ sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
|||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->open(sink)) {
|
if (!sink->ops->open(sink)) {
|
||||||
|
LOGE("Could not open frame sink %d", i);
|
||||||
sc_decoder_close_first_sinks(decoder, i);
|
sc_decoder_close_first_sinks(decoder, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -62,6 +63,7 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_decoder_open_sinks(decoder)) {
|
if (!sc_decoder_open_sinks(decoder)) {
|
||||||
|
LOGE("Could not open decoder sinks");
|
||||||
av_frame_free(&decoder->frame);
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
@@ -84,6 +86,7 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
|||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->push(sink, frame)) {
|
if (!sink->ops->push(sink, frame)) {
|
||||||
|
LOGE("Could not send frame to sink %d", i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
|||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||||
if (!sink->ops->push(sink, packet)) {
|
if (!sink->ops->push(sink, packet)) {
|
||||||
|
LOGE("Could not send packet to sink %d", i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +129,7 @@ static bool
|
|||||||
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||||
bool ok = push_packet_to_sinks(demuxer, packet);
|
bool ok = push_packet_to_sinks(demuxer, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Demuxer '%s': could not process packet", demuxer->name);
|
LOGE("Could not process packet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +154,7 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
|||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||||
if (!sink->ops->open(sink, codec)) {
|
if (!sink->ops->open(sink, codec)) {
|
||||||
|
LOGE("Could not open packet sink %d", i);
|
||||||
sc_demuxer_close_first_sinks(demuxer, i);
|
sc_demuxer_close_first_sinks(demuxer, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -181,15 +183,12 @@ run_demuxer(void *data) {
|
|||||||
uint32_t raw_codec_id;
|
uint32_t raw_codec_id;
|
||||||
bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id);
|
bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Demuxer '%s': stream disabled due to connection error",
|
|
||||||
demuxer->name);
|
|
||||||
eos = true;
|
eos = true;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raw_codec_id == 0) {
|
if (raw_codec_id == 0) {
|
||||||
LOGW("Demuxer '%s': stream explicitly disabled by the device",
|
// Stream explicitly disabled by the device
|
||||||
demuxer->name);
|
|
||||||
sc_demuxer_disable_sinks(demuxer);
|
sc_demuxer_disable_sinks(demuxer);
|
||||||
eos = true;
|
eos = true;
|
||||||
goto end;
|
goto end;
|
||||||
@@ -197,21 +196,18 @@ run_demuxer(void *data) {
|
|||||||
|
|
||||||
enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id);
|
enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id);
|
||||||
if (codec_id == AV_CODEC_ID_NONE) {
|
if (codec_id == AV_CODEC_ID_NONE) {
|
||||||
LOGE("Demuxer '%s': stream disabled due to unsupported codec",
|
// Error already logged
|
||||||
demuxer->name);
|
|
||||||
sc_demuxer_disable_sinks(demuxer);
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVCodec *codec = avcodec_find_decoder(codec_id);
|
const AVCodec *codec = avcodec_find_decoder(codec_id);
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
LOGE("Demuxer '%s': stream disabled due to missing decoder",
|
LOGE("Decoder not found");
|
||||||
demuxer->name);
|
|
||||||
sc_demuxer_disable_sinks(demuxer);
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
||||||
|
LOGE("Could not open demuxer sinks");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +252,7 @@ run_demuxer(void *data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Demuxer '%s': end of frames", demuxer->name);
|
LOGD("End of frames");
|
||||||
|
|
||||||
if (must_merge_config_packet) {
|
if (must_merge_config_packet) {
|
||||||
sc_packet_merger_destroy(&merger);
|
sc_packet_merger_destroy(&merger);
|
||||||
@@ -272,11 +268,10 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
|
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
|
||||||
assert(socket != SC_SOCKET_NONE);
|
assert(socket != SC_SOCKET_NONE);
|
||||||
|
|
||||||
demuxer->name = name; // statically allocated
|
|
||||||
demuxer->socket = socket;
|
demuxer->socket = socket;
|
||||||
demuxer->sink_count = 0;
|
demuxer->sink_count = 0;
|
||||||
|
|
||||||
@@ -296,12 +291,12 @@ sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
||||||
LOGD("Demuxer '%s': starting thread", demuxer->name);
|
LOGD("Starting demuxer thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
|
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
|
||||||
demuxer);
|
demuxer);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Demuxer '%s': could not start thread", demuxer->name);
|
LOGE("Could not start demuxer thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
#define SC_DEMUXER_MAX_SINKS 2
|
#define SC_DEMUXER_MAX_SINKS 2
|
||||||
|
|
||||||
struct sc_demuxer {
|
struct sc_demuxer {
|
||||||
const char *name; // must be statically allocated (e.g. a string literal)
|
|
||||||
|
|
||||||
sc_socket socket;
|
sc_socket socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
|
|
||||||
@@ -31,9 +29,8 @@ struct sc_demuxer_callbacks {
|
|||||||
void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata);
|
void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
// The name must be statically allocated (e.g. a string literal)
|
|
||||||
void
|
void
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -249,9 +249,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
|||||||
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) {
|
if (recorder->stopped && sc_recorder_has_empty_queues(recorder)) {
|
||||||
// If the recorder is stopped, don't process anything if there are not
|
|
||||||
// at least video packets
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -259,9 +257,8 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
|||||||
struct sc_record_packet *video_pkt;
|
struct sc_record_packet *video_pkt;
|
||||||
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
||||||
|
|
||||||
struct sc_record_packet *audio_pkt = NULL;
|
struct sc_record_packet *audio_pkt;
|
||||||
if (!sc_queue_is_empty(&recorder->audio_queue)) {
|
if (recorder->audio) {
|
||||||
assert(recorder->audio);
|
|
||||||
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +279,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audio_pkt) {
|
if (recorder->audio) {
|
||||||
if (audio_pkt->packet->pts != AV_NOPTS_VALUE) {
|
if (audio_pkt->packet->pts != AV_NOPTS_VALUE) {
|
||||||
LOGE("The first audio packet is not a config packet");
|
LOGE("The first audio packet is not a config packet");
|
||||||
goto end;
|
goto end;
|
||||||
@@ -307,7 +304,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
|||||||
|
|
||||||
end:
|
end:
|
||||||
sc_record_packet_delete(video_pkt);
|
sc_record_packet_delete(video_pkt);
|
||||||
if (audio_pkt) {
|
if (recorder->audio) {
|
||||||
sc_record_packet_delete(audio_pkt);
|
sc_record_packet_delete(audio_pkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +353,12 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
|||||||
assert(recorder->audio
|
assert(recorder->audio
|
||||||
|| (!audio_pkt && sc_queue_is_empty(&recorder->audio_queue)));
|
|| (!audio_pkt && sc_queue_is_empty(&recorder->audio_queue)));
|
||||||
|
|
||||||
|
if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)
|
||||||
|
&& sc_queue_is_empty(&recorder->audio_queue)) {
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
||||||
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
||||||
}
|
}
|
||||||
@@ -364,13 +367,6 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
|||||||
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recorder->stopped && !video_pkt && !audio_pkt) {
|
|
||||||
assert(sc_queue_is_empty(&recorder->video_queue));
|
|
||||||
assert(sc_queue_is_empty(&recorder->audio_queue));
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(video_pkt || audio_pkt); // at least one
|
assert(video_pkt || audio_pkt); // at least one
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
@@ -395,21 +391,6 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
|||||||
} else if (video_pkt && audio_pkt) {
|
} else if (video_pkt && audio_pkt) {
|
||||||
pts_origin =
|
pts_origin =
|
||||||
MIN(video_pkt->packet->pts, audio_pkt->packet->pts);
|
MIN(video_pkt->packet->pts, audio_pkt->packet->pts);
|
||||||
} else if (recorder->stopped) {
|
|
||||||
if (video_pkt) {
|
|
||||||
// The recorder is stopped without audio, record the video
|
|
||||||
// packets
|
|
||||||
pts_origin = video_pkt->packet->pts;
|
|
||||||
} else {
|
|
||||||
// Fail if there is no video
|
|
||||||
error = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
// If the recorder is stopped while one of the streams has no
|
|
||||||
// packets, then we must avoid a live-loop and correctly record
|
|
||||||
// the stream having packets.
|
|
||||||
pts_origin = video_pkt ? video_pkt->packet->pts
|
|
||||||
: audio_pkt->packet->pts;
|
|
||||||
} else {
|
} else {
|
||||||
// We need both video and audio packets to initialize pts_origin
|
// We need both video and audio packets to initialize pts_origin
|
||||||
continue;
|
continue;
|
||||||
@@ -432,8 +413,8 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
|||||||
sc_record_packet_delete(video_pkt_previous);
|
sc_record_packet_delete(video_pkt_previous);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not record video packet");
|
LOGE("Could not record video packet");
|
||||||
error = true;
|
// TODO cleanup
|
||||||
goto end;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,13 +427,13 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
|||||||
audio_pkt->packet->dts = audio_pkt->packet->pts;
|
audio_pkt->packet->dts = audio_pkt->packet->pts;
|
||||||
|
|
||||||
bool ok = sc_recorder_write_audio(recorder, audio_pkt->packet);
|
bool ok = sc_recorder_write_audio(recorder, audio_pkt->packet);
|
||||||
|
sc_record_packet_delete(audio_pkt);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not record audio packet");
|
LOGE("Could not record audio packet");
|
||||||
error = true;
|
error = true;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_record_packet_delete(audio_pkt);
|
|
||||||
audio_pkt = NULL;
|
audio_pkt = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,7 +657,7 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
|
|||||||
assert(!recorder->audio_disabled);
|
assert(!recorder->audio_disabled);
|
||||||
assert(!recorder->audio_codec);
|
assert(!recorder->audio_codec);
|
||||||
|
|
||||||
LOGW("Audio stream recording disabled");
|
LOGW("Audio stream disabled: it could not be captured by the device");
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->audio_disabled = true;
|
recorder->audio_disabled = true;
|
||||||
|
|||||||
@@ -463,14 +463,14 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
static const struct sc_demuxer_callbacks video_demuxer_cbs = {
|
static const struct sc_demuxer_callbacks video_demuxer_cbs = {
|
||||||
.on_ended = sc_video_demuxer_on_ended,
|
.on_ended = sc_video_demuxer_on_ended,
|
||||||
};
|
};
|
||||||
sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket,
|
sc_demuxer_init(&s->video_demuxer, s->server.video_socket,
|
||||||
&video_demuxer_cbs, NULL);
|
&video_demuxer_cbs, NULL);
|
||||||
|
|
||||||
if (options->audio) {
|
if (options->audio) {
|
||||||
static const struct sc_demuxer_callbacks audio_demuxer_cbs = {
|
static const struct sc_demuxer_callbacks audio_demuxer_cbs = {
|
||||||
.on_ended = sc_audio_demuxer_on_ended,
|
.on_ended = sc_audio_demuxer_on_ended,
|
||||||
};
|
};
|
||||||
sc_demuxer_init(&s->audio_demuxer, "audio", s->server.audio_socket,
|
sc_demuxer_init(&s->audio_demuxer, s->server.audio_socket,
|
||||||
&audio_demuxer_cbs, NULL);
|
&audio_demuxer_cbs, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import java.util.concurrent.BlockingQueue;
|
|||||||
public final class AudioEncoder {
|
public final class AudioEncoder {
|
||||||
|
|
||||||
private static class InputTask {
|
private static class InputTask {
|
||||||
private final int index;
|
final int index;
|
||||||
|
|
||||||
InputTask(int index) {
|
InputTask(int index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
@@ -28,8 +28,8 @@ public final class AudioEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class OutputTask {
|
private static class OutputTask {
|
||||||
private final int index;
|
final int index;
|
||||||
private final MediaCodec.BufferInfo bufferInfo;
|
final MediaCodec.BufferInfo bufferInfo;
|
||||||
|
|
||||||
OutputTask(int index, MediaCodec.BufferInfo bufferInfo) {
|
OutputTask(int index, MediaCodec.BufferInfo bufferInfo) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ package com.genymobile.scrcpy;
|
|||||||
|
|
||||||
public interface Codec {
|
public interface Codec {
|
||||||
|
|
||||||
enum Type {
|
enum Type {VIDEO, AUDIO}
|
||||||
VIDEO,
|
|
||||||
AUDIO,
|
|
||||||
}
|
|
||||||
|
|
||||||
Type getType();
|
Type getType();
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ public final class Server {
|
|||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("MethodLength")
|
|
||||||
private static Options createOptions(String... args) {
|
private static Options createOptions(String... args) {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
throw new IllegalArgumentException("Missing client version");
|
throw new IllegalArgumentException("Missing client version");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.app.Instrumentation;
|
||||||
import android.content.ContextWrapper;
|
import android.content.ContextWrapper;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|||||||
Reference in New Issue
Block a user