blob: e68d2455db26f6efaa6de947d38cd43d4fd24e01 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "android/emuctl-client.h"
#include "android/base/ArraySize.h"
#include "android/base/memory/LazyInstance.h"
#include "android/base/sockets/SocketUtils.h"
#include "android/base/sockets/ScopedSocket.h"
#include "android/base/async/ScopedSocketWatch.h"
#include "android/base/async/ThreadLooper.h"
#include "android/emulation/control/sensors_agent.h"
#include "android/globals.h"
#include "android/hw-sensors.h"
#include "android/skin/rect.h"
#include "android/skin/linux_keycodes.h"
#include "android/multitouch-screen.h"
#include "android/jpeg-compress.h"
#include "android/gpu_frame.h"
#include <cmath>
#include <limits>
#include <errno.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <netinet/in.h>
#endif
enum PacketType {
PACKET_TYPE_SENSOR_DATA_ACCELEROMETER = (0x01),
PACKET_TYPE_SENSOR_DATA_MAGNETOMETER = (0x02),
PACKET_TYPE_SENSOR_DATA_ORIENTATION = (0x03), /* deprecated */
PACKET_TYPE_SENSOR_DATA_AMBIENT_TEMPERATURE = (0x04),
PACKET_TYPE_SENSOR_DATA_PROXIMITY = (0x05),
PACKET_TYPE_SENSOR_DATA_LIGHT = (0x06),
PACKET_TYPE_SENSOR_DATA_PRESSURE = (0x07),
PACKET_TYPE_SENSOR_DATA_HUMIDITY = (0x08),
PACKET_TYPE_MT_ACTION_DOWN = (0x09),
PACKET_TYPE_MT_ACTION_MOVE = (0x0A),
PACKET_TYPE_MT_ACTION_UP = (0x0B),
PACKET_TYPE_MT_ACTION_CANCEL = (0x0C)
};
namespace {
// A data packet sent by the controller app.
// All values are in network byte order.
struct InboundPacket {
// One of the PACKET_TYPE_* constants, indicates what data is contained
// in this packet.
uint32_t type;
// For SENSOR_DATA_* data packets, x, y and z are the readings of
// the sensors.
// For MT_ACTION_* packets, x and y contain the coordinates of the touch
// event in screen pixels, while z is the pressure.
float x;
float y;
float z;
// For MT_ACTION_* packets, tracking_id contains the pointer id.
uint32_t tracking_id;
};
// A header that is sent to the controller app with every screen update.
struct FramebufferPacketHeader {
uint32_t buf_size;
uint32_t screen_orientation;
};
struct Globals {
::android::base::Looper* looper;
const QAndroidSensorsAgent* sensors;
::android::base::ScopedSocketWatch socket;
uint8_t pkt_buf[sizeof(InboundPacket)];
int pkt_bytes_remaining;
int pkt_bytes_read;
SkinRotation last_known_screen_orientation;
AJPEGDesc* jpeg_compressor;
};
android::base::LazyInstance<Globals> sGlobals = LAZY_INSTANCE_INIT;
// Converts a float from network to host byte-order.
inline static float float_ntohl(float a) {
union {
float f;
uint32_t i;
} v;
v.f = a;
v.i = ntohl(v.i);
return v.f;
}
// Helper function for sending screen updates to the controller app.
// It tries to guess the current screen orientation of the device from
// the accelerometer values.
// This method is not ideal, although it works in the majority of cases.
// It relies on gravity giving the most significant
// contribution to the acceleration vector. Also, some apps might lock the
// screen in a particular orientation so that it's not affected by the
// accelerometer at all, which isn't taken into account by this function.
// TODO: implement a method for retrieving the actual screen orientation from
// the emulated device.
static SkinRotation guessScreenOrientation() {
// Values for gravity in different orientations
float gravity[][3] = {
// Face up/down
{0.0f, 0.0f, 9.8f},
{0.0f, 0.0f, -9.8f},
// Portrait/reverse portrait
{0.0f, 9.8f, 0.0f},
{0.0f, -9.8f, 0.0f},
// Landscape/reverse landscape
{9.8f, 0.0f, 0.0f},
{-9.8f, 0.0f, 0.0f}
};
SkinRotation orientations[] = {
SKIN_ROTATION_0,
SKIN_ROTATION_180,
SKIN_ROTATION_270,
SKIN_ROTATION_90
};
float min_diff_norm = std::numeric_limits<float>::max();
int gravity_direction_index = 2;
float acceleration[3];
sGlobals->sensors->getSensor(
ANDROID_SENSOR_ACCELERATION,
&acceleration[0],
&acceleration[1],
&acceleration[2]);
for (size_t i = 0; i < ARRAY_SIZE(gravity); i++) {
float diff_component = 0.0f;
float diff_norm = 0.0f;
for (size_t j = 0; j < ARRAY_SIZE(acceleration); j++) {
diff_component = acceleration[j] - gravity[i][j];
diff_component *= diff_component;
diff_norm += diff_component;
}
if (min_diff_norm > diff_norm) {
min_diff_norm = diff_norm;
gravity_direction_index = i;
}
}
// Lying face up/down doesn't change the screen orientation.
// In other cases, update the last known orientation
if (gravity_direction_index > 1) {
sGlobals->last_known_screen_orientation =
orientations[gravity_direction_index - 2];
}
return sGlobals->last_known_screen_orientation;
}
// This function gets called every time the contents of the window changes.
static void onFramebufferPosted(void*, int w, int h, const void* pixels) {
// We expect screen contents to in RGB format, one byte per channel.
static const int bytes_per_pixel = 3;
static const int jpeg_quality = 50;
static const int direction = 1;
if (sGlobals->socket) {
// JPEG-compress the contents of the window.
const uint8_t* fb = static_cast<const uint8_t*>(pixels);
jpeg_compressor_compress_fb(
sGlobals->jpeg_compressor,
0, 0, w, h,
h,
bytes_per_pixel, w * bytes_per_pixel,
fb,
jpeg_quality,
direction);
void* compr_buf =
jpeg_compressor_get_buffer(sGlobals->jpeg_compressor);
// Fill out header fields.
FramebufferPacketHeader* hdr =
static_cast<FramebufferPacketHeader*>(compr_buf);
hdr->buf_size =
jpeg_compressor_get_jpeg_size(sGlobals->jpeg_compressor);
hdr->screen_orientation =
static_cast<int>(guessScreenOrientation());
// Send the update to the controller app.
android::base::socketSend(
sGlobals->socket->fd(),
compr_buf,
sizeof(FramebufferPacketHeader) + hdr->buf_size);
}
}
// Helper to convert touch coordinates in screen pixels into ones used by
// the system.
static int scaleForTouchpad(float in, int axis_in) {
// Constant defined in QEMU2 glue code, redefining it here to avoid
// including QEMU2-specific headers.
static const int touchpad_size = 0x8000;
return in * (touchpad_size-1) / (axis_in-1);
}
// Handles an incoming packet.
static void handlePacket(InboundPacket* p) {
uint32_t type = ntohl(p->type);
if (type < PACKET_TYPE_MT_ACTION_DOWN && sGlobals->sensors) {
// Sensor data packet.
sGlobals->sensors->setSensor(
ANDROID_SENSOR_ACCELERATION + (type-1),
float_ntohl(p->x),
float_ntohl(p->y),
float_ntohl(p->z));
} else {
// Multitouch data packet.
int x =
scaleForTouchpad(float_ntohl(p->x), android_hw->hw_lcd_width);
int y =
scaleForTouchpad(float_ntohl(p->y), android_hw->hw_lcd_height);
int pressure = static_cast<int>(100.0f * float_ntohl(p->z));
switch (type) {
case PACKET_TYPE_MT_ACTION_DOWN:
case PACKET_TYPE_MT_ACTION_MOVE:
multitouch_update_pointer(
MTES_DEVICE,
ntohl(p->tracking_id),
x,
y,
pressure,
true);
break;
case PACKET_TYPE_MT_ACTION_UP:
case PACKET_TYPE_MT_ACTION_CANCEL:
multitouch_update_pointer(
MTES_DEVICE,
ntohl(p->tracking_id),
0,
0,
0,
true);
break;
}
}
}
// Called when there's some data available to read from the controller app
// connection.
static void onDataAvailable(void* opaque, int sock, unsigned int) {
ssize_t size = 0;
bool first_read = true;
// Read as much data as possible. As soon as we get enough bytes
// for a packet, process that packet.
do {
size =
::android::base::socketRecv(
sock,
sGlobals->pkt_buf + sGlobals->pkt_bytes_read,
sGlobals->pkt_bytes_remaining);
if (first_read && size == 0) {
// This happens when the remote side closes connection.
android_emuctl_client_disconnect();
return;
}
first_read = false;
if (size > 0) {
sGlobals->pkt_bytes_remaining -= size;
sGlobals->pkt_bytes_read += size;
if (sGlobals->pkt_bytes_remaining == 0) {
InboundPacket* p =
reinterpret_cast<InboundPacket*>(&sGlobals->pkt_buf);
handlePacket(p);
sGlobals->pkt_bytes_remaining = sizeof(InboundPacket);
sGlobals->pkt_bytes_read = 0;
}
}
} while (size > 0);
if (size < 0 && (errno != EAGAIN || errno != EWOULDBLOCK)) {
android_emuctl_client_disconnect();
}
}
} // namespace
void android_emuctl_client_init(void) {
sGlobals->looper = android::base::ThreadLooper::get();
sGlobals->sensors = nullptr;
sGlobals->last_known_screen_orientation = SKIN_ROTATION_0;
sGlobals->jpeg_compressor =
jpeg_compressor_create(
sizeof(FramebufferPacketHeader),
4096);
}
void android_emuctl_client_connect(int port) {
android_emuctl_client_disconnect();
android::base::ScopedSocket sock (
android::base::socketTcp4LoopbackClient(port));
android::base::socketSetNonBlocking(sock.get());
if (sock.get() > 0) {
sGlobals->pkt_bytes_remaining = sizeof(InboundPacket);
sGlobals->pkt_bytes_read = 0;
sGlobals->socket.reset(
sGlobals->looper->createFdWatch(
sock.release(),
onDataAvailable,
nullptr));
sGlobals->socket->wantRead();
gpu_frame_set_post_callback(
reinterpret_cast<Looper*>(sGlobals->looper),
nullptr,
onFramebufferPosted);
}
}
void android_emuctl_client_disconnect(void) {
gpu_frame_set_post_callback(
reinterpret_cast<Looper*>(sGlobals->looper),
nullptr,
nullptr);
sGlobals->socket.reset(0);
}
void android_emuctl_client_setsensors(const QAndroidSensorsAgent* agent) {
sGlobals->sensors = agent;
}