diff options
author | Vladimir Chtchetkine <vchtchetkine@google.com> | 2011-11-01 17:35:07 -0700 |
---|---|---|
committer | Vladimir Chtchetkine <vchtchetkine@google.com> | 2011-11-09 18:13:02 -0800 |
commit | db611d57e0da9acd7ecf2a4a9b2a63e7620fe54d (patch) | |
tree | 66f81dbd4ae02a000478036b0d7210050929d564 | |
parent | 6f3bc59f5099cdce5a939a5ab9ba33f3d7f2273e (diff) | |
download | external_qemu-db611d57e0da9acd7ecf2a4a9b2a63e7620fe54d.zip external_qemu-db611d57e0da9acd7ecf2a4a9b2a63e7620fe54d.tar.gz external_qemu-db611d57e0da9acd7ecf2a4a9b2a63e7620fe54d.tar.bz2 |
Implements sensors emulation using a connected Android device
There are three major things in this CL:
1. Abstract a connection with an Android device that is connected to the host via
USB, and there is a TCP port forwarding to this device via 'adb forward' command.
This abstraction is implemented in android/android-device.*
2. A client for android device API that talks to an app on the connected device that
provides values for sensors available on the device. This is implemented in
android/sensors-port.*
3. Changes to the sensor emulation code in android/hw-sensors.c to use sensors port
(when available) for sensors emulation.
Change-Id: I12901e8db6b6a6262fc1703ed96a9f714335d666
-rw-r--r-- | Makefile.common | 2 | ||||
-rw-r--r-- | android/android-device.c | 1281 | ||||
-rw-r--r-- | android/android-device.h | 239 | ||||
-rw-r--r-- | android/async-utils.c | 19 | ||||
-rw-r--r-- | android/async-utils.h | 12 | ||||
-rw-r--r-- | android/avd/hardware-properties.ini | 21 | ||||
-rw-r--r-- | android/hw-sensors.c | 53 | ||||
-rw-r--r-- | android/sensors-port.c | 341 | ||||
-rw-r--r-- | android/sensors-port.h | 91 | ||||
-rw-r--r-- | android/utils/debug.h | 2 |
10 files changed, 2048 insertions, 13 deletions
diff --git a/Makefile.common b/Makefile.common index 71100f3..45be256 100644 --- a/Makefile.common +++ b/Makefile.common @@ -423,6 +423,8 @@ CORE_MISC_SOURCES = \ android/hw-pipe-net.c \ android/qemu-setup.c \ android/snapshot.c \ + android/android-device.c \ + android/sensors-port.c \ android/utils/timezone.c \ android/camera/camera-format-converters.c \ android/camera/camera-service.c diff --git a/android/android-device.c b/android/android-device.c new file mode 100644 index 0000000..093879d --- /dev/null +++ b/android/android-device.c @@ -0,0 +1,1281 @@ +/* + * Copyright (C) 2011 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. + */ + +/* + * Encapsulates exchange protocol between the emulator, and an Android device + * that is connected to the host via USB. The communication is established over + * a TCP port forwarding, enabled by ADB. + */ + +#include "android/android-device.h" +#include "iolooper.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(adevice,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(adevice) + +/******************************************************************************** + * Common android device socket + *******************************************************************************/ + +/* Milliseconds between retrying asynchronous connections to the device. */ +#define ADS_RETRY_CONNECTION_TIMEOUT 3000 + +/* Socket type. */ +typedef enum ADSType { + /* Query socket. */ + ADS_TYPE_QUERY = 0, + /* Events socket. */ + ADS_TYPE_EVENT = 1 +} ADSType; + +/* Status of the socket. */ +typedef enum ADSStatus { + /* Socket is disconnected. */ + ADS_DISCONNECTED, + /* Connection process has been started. */ + ADS_CONNECTING, + /* Connection has been established. */ + ADS_CONNECTED, + /* Socket has been registered with the server. */ + ADS_REGISTERED, +} ADSStatus; + +/* Identifies socket as a "query" socket with the server. */ +static const char* _ads_query_socket_id = "query"; +/* Identifies socket as an "event" socket with the server. */ +static const char* _ads_event_socket_id = "event"; + +/* Android device socket descriptor. */ +typedef struct AndroidDevSocket AndroidDevSocket; + +/* + * Callback routines. + */ + +/* Callback routine that is called when a socket is connected. + * Param: + * opaque - Opaque pointer associated with the socket. Typicaly it's the same + * pointer that is associated with AndroidDevice instance. + * ads - Connection socket. + * failure - If zero, indicates that socket has been successuly connected. If a + * connection error has occured, this parameter contains the error code (as + * in 'errno). + */ +typedef void (*ads_socket_connected_cb)(void* opaque, + struct AndroidDevSocket* ads, + int failure); + +/* Android device socket descriptor. */ +struct AndroidDevSocket { + /* Socket type. */ + ADSType type; + /* Socket status. */ + ADSStatus socket_status; + /* TCP address for the socket. */ + SockAddress address; + /* Android device descriptor that owns the socket. */ + AndroidDevice* ad; + /* Opaque pointer associated with the socket. Typicaly it's the same + * pointer that is associated with AndroidDevice instance.*/ + void* opaque; + /* Deadline for current I/O performed on the socket. */ + Duration deadline; + /* Socket's file descriptor. */ + int fd; +}; + +/* Query socket descriptor. */ +typedef struct AndroidQuerySocket { + /* Common device socket. */ + AndroidDevSocket dev_socket; +} AndroidQuerySocket; + +/* Event socket descriptor. */ +typedef struct AndroidEventSocket { + /* Common socket descriptor. */ + AndroidDevSocket dev_socket; + /* Asynchronous connector to the device. */ + AsyncConnector connector[1]; + /* I/O port for asynchronous I/O on this socket. */ + LoopIo io[1]; + /* Asynchronous string reader. */ + AsyncLineReader alr; + /* Callback to call at the end of the asynchronous connection to this socket. + * Can be NULL. */ + ads_socket_connected_cb on_connected; + /* Callback to call when an event is received on this socket. Can be NULL. */ + event_cb on_event; +} AndroidEventSocket; + +/* Android device descriptor. */ +struct AndroidDevice { + /* Query socket for the device. */ + AndroidQuerySocket query_socket; + /* Event socket for the device. */ + AndroidEventSocket event_socket; + /* An opaque pointer associated with this descriptor. */ + void* opaque; + /* I/O looper for synchronous I/O on the sockets for this device. */ + IoLooper* io_looper; + /* Timer that is used to retry asynchronous connections. */ + LoopTimer timer[1]; + /* I/O looper for asynchronous I/O. */ + Looper* looper; + /* Callback to call when device is fully connected. */ + device_connected_cb on_connected; + /* I/O failure callback .*/ + io_failure_cb on_io_failure; +}; + +/******************************************************************************** + * Common socket declarations + *******************************************************************************/ + +/* Initializes common device socket. + * Param: + * ads - Socket descriptor to initialize. + * opaque - An opaque pointer to associate with the socket. Typicaly it's the + * same pointer that is associated with AndroidDevice instance. + * ad - Android device descriptor that owns the socket. + * port - Socket's TCP port. + * type - Socket type (query, or event). + */ +static int _android_dev_socket_init(AndroidDevSocket* ads, + void* opaque, + AndroidDevice* ad, + int port, + ADSType type); + +/* Destroys socket descriptor. */ +static void _android_dev_socket_destroy(AndroidDevSocket* ads); + +/* Synchronously connects to the socket, and registers it with the server. + * Param: + * ads - Socket to connect. Must have 'deadline' field properly setup. + * Return: + * 0 on success, -1 on failure with errno containing the reason for failure. + */ +static int _android_dev_socket_connect(AndroidDevSocket* ads); + +/* Synchronously registers a connected socket with the server. + * Param: + * ads - Socket to register. Must be connected, and must have 'deadline' field + * properly setup. + * Return: + * 0 on success, -1 on failure with errno containing the reason for failure. + */ +static int _android_dev_socket_register(AndroidDevSocket* ads); + +/* Disconnects the socket (if it was connected) */ +static void _android_dev_socket_disconnect(AndroidDevSocket* ads); + +/* Synchronously sends data to the socket. + * Param: + * ads - Socket to send the data to. Must be connected, and must have 'deadline' + * field properly setup. + * buff, buffsize - Buffer to send. + * Return: + * Number of bytes sent on success, or -1 on failure with errno containing the + * reason for failure. + */ +static int _android_dev_socket_send(AndroidDevSocket* ads, + const char* buff, + int buffsize); + +/* Synchronously receives data from the socket. + * Param: + * ads - Socket to receive the data from. Must be connected, and must have + * 'deadline' field properly setup. + * buff, buffsize - Buffer where to receive data. + * Return: + * Number of bytes received on success, or -1 on failure with errno containing + * the reason for failure. + */ +static int _android_dev_socket_recv(AndroidDevSocket* ads, + char* buf, + int bufsize); + +/* Synchronously reads zero-terminated string from the socket. + * Param: + * ads - Socket to read the string from. Must be connected, and must have + * 'deadline' field properly setup. + * str, strsize - Buffer where to read the string. + * Return: + * Number of charactes read into the string buffer (including zero-terminator) + * on success, or -1 on failure with 'errno' containing the reason for failure. + * If this routine returns -1, and errno contains ENOMEM, this is an indicator + * that supplied string buffer was too small for the receiving string. + */ +static int _android_dev_socket_read_string(AndroidDevSocket* ads, + char* str, + int strsize); + +/* Synchronously reads zero-terminated query response from the socket. + * All queries respond with an 'ok', or 'ko' prefix, indicating a success, or + * failure. Prefix can be followed by more query response data, separated from + * the prefix with a ':' character. This routine helps separating prefix from the + * data, by placing only the query response data into provided buffer. 'ko' or + * 'ok' will be encoded in the return value. + * Param: + * ads - Socket to read the response from. Must be connected, and must have + * 'deadline' field properly setup. + * data, datasize - Buffer where to read the query response data. + * Return: + * Number of charactes read into the data buffer (including zero-terminator) on + * success, or -1 on failure with errno containing the reason for failure. + * If the query has been completed with 'ko', this routine will return -1, with + * errno set to 0. If this routine returned -1, and errno is set to EINVAL, this + * indicates that reply string didn't match expected query reply format. + */ +static int _android_dev_socket_read_response(AndroidDevSocket* ads, + char* str, + int strsize); + +/* Gets ID string for the channel. */ +AINLINED const char* +_ads_id_str(AndroidDevSocket* ads) +{ + return (ads->type == ADS_TYPE_QUERY) ? _ads_query_socket_id : + _ads_event_socket_id; +} + +/* Gets socket's TCP port. */ +AINLINED int +_ads_port(AndroidDevSocket* ads) +{ + return sock_address_get_port(&ads->address); +} + +/* Gets synchronous I/O looper for the socket. */ +AINLINED IoLooper* +_ads_io_looper(AndroidDevSocket* ads) +{ + return ads->ad->io_looper; +} + +/* Sets deadline on a socket operation, given relative timeout. + * Param: + * ads - Socket descriptor to set deadline for. + * to - Relative timeout (in millisec) for the operation. + * AD_INFINITE_WAIT passed in this parameter means "no deadline". + */ +AINLINED void +_ads_set_deadline(AndroidDevSocket* ads, int to) +{ + ads->deadline = (to == AD_INFINITE_WAIT) ? DURATION_INFINITE : + iolooper_now() + to; +} + +/******************************************************************************** + * Common socket implementation + *******************************************************************************/ + +static int +_android_dev_socket_init(AndroidDevSocket* ads, + void* opaque, + AndroidDevice* ad, + int port, + ADSType type) +{ + ads->type = type; + ads->socket_status = ADS_DISCONNECTED; + ads->opaque = opaque; + ads->ad = ad; + ads->fd = -1; + sock_address_init_inet(&ads->address, SOCK_ADDRESS_INET_LOOPBACK, port); + + return 0; +} + +static void +_android_dev_socket_destroy(AndroidDevSocket* ads) +{ + /* Make sure it's disconnected. */ + _android_dev_socket_disconnect(ads); + + /* Finalize socket address. */ + sock_address_done(&ads->address); + memset(&ads->address, 0, sizeof(ads->address)); +} + +static int +_android_dev_socket_connect(AndroidDevSocket* ads) +{ + int res; + + /* Create communication socket. */ + ads->fd = socket_create_inet(SOCKET_STREAM); + if (ads->fd < 0) { + D("Unable to create socket for channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + return -1; + } + socket_set_nonblock(ads->fd); + + /* Synchronously connect to it. */ + ads->socket_status = ADS_CONNECTING; + iolooper_add_write(_ads_io_looper(ads), ads->fd); + res = socket_connect(ads->fd, &ads->address); + while (res < 0 && errno == EINTR) { + res = socket_connect(ads->fd, &ads->address); + } + + if (res && (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN)) { + /* Connection is delayed. Wait for it until timeout expires. */ + res = iolooper_wait_absolute(_ads_io_looper(ads), ads->deadline); + if (res > 0) { + /* Pick up on possible connection error. */ + errno = socket_get_error(ads->fd); + res = (errno == 0) ? 0 : -1; + } else { + res = -1; + } + } + iolooper_del_write(_ads_io_looper(ads), ads->fd); + + if (res == 0) { + D("Channel '%s'@%d is connected", _ads_id_str(ads), _ads_port(ads)); + /* Socket is connected. Now register it with the server. */ + ads->socket_status = ADS_CONNECTED; + res = _android_dev_socket_register(ads); + } else { + D("Unable to connect channel '%s' to port %d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + } + + if (res) { + _android_dev_socket_disconnect(ads); + } + + return res; +} + +static int +_android_dev_socket_register(AndroidDevSocket* ads) +{ + /* Make sure that socket is connected. */ + if (ads->socket_status < ADS_CONNECTED) { + D("Attempt to register a disconnected channel '%s'@%d", + _ads_id_str(ads), _ads_port(ads)); + errno = ECONNRESET; + return -1; + } + + /* Register this socket accordingly to its type. */ + const char* reg_str = _ads_id_str(ads); + int res = _android_dev_socket_send(ads, reg_str, strlen(reg_str) + 1); + if (res > 0) { + /* Receive reply. Note that according to the protocol, the server should + * reply to channel registration with 'ok', or 'ko' (just like with queries), + * so we can use query reply reader here. */ + char reply[256]; + res = _android_dev_socket_read_response(ads, reply, sizeof(reply)); + if (res >= 0) { + /* Socket is now registered. */ + ads->socket_status = ADS_REGISTERED; + D("Channel '%s'@%d is registered", _ads_id_str(ads), _ads_port(ads)); + res = 0; + } else { + if (errno == 0) { + /* 'ko' condition */ + D("Device failed registration of channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), reply); + errno = EINVAL; + } else { + D("I/O failure while registering channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + } + res = -1; + } + } else { + D("Unable to send registration query for channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + res = -1; + } + + return res; +} + +static void +_android_dev_socket_disconnect(AndroidDevSocket* ads) +{ + /* Preserve errno */ + const int save_error = errno; + if (ads->socket_status != ADS_DISCONNECTED) { + /* Reset I/O looper for this socket. */ + iolooper_modify(_ads_io_looper(ads), ads->fd, + IOLOOPER_READ | IOLOOPER_WRITE, 0); + + /* Mark as disconnected. */ + ads->socket_status = ADS_DISCONNECTED; + + /* Close socket. */ + if (ads->fd >= 0) { + socket_close(ads->fd); + ads->fd = -1; + } + } + errno = save_error; +} + +static int +_android_dev_socket_send(AndroidDevSocket* ads, const char* buff, int to_send) +{ + int sent = 0; + + /* Make sure that socket is connected. */ + if (ads->socket_status < ADS_CONNECTED) { + D("Attempt to send via disconnected channel '%s'@%d", + _ads_id_str(ads), _ads_port(ads)); + errno = ECONNRESET; + return -1; + } + + iolooper_add_write(_ads_io_looper(ads), ads->fd); + do { + int res = socket_send(ads->fd, buff + sent, to_send - sent); + if (res == 0) { + /* Disconnection. */ + errno = ECONNRESET; + sent = -1; + break; + } + + if (res < 0) { + if (errno == EINTR) { + /* loop on EINTR */ + continue; + } + + if (errno == EWOULDBLOCK || errno == EAGAIN) { + res = iolooper_wait_absolute(_ads_io_looper(ads), ads->deadline); + if (res > 0) { + /* Ready to write. */ + continue; + } + } + sent = -1; + break; + } + sent += res; + } while (sent < to_send); + iolooper_del_write(_ads_io_looper(ads), ads->fd); + + /* In case of an I/O failure we have to invoke failure callback. Note that we + * report I/O failures only on registered sockets. */ + if (sent < 0) { + D("I/O error while sending data via channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + + if (ads->ad->on_io_failure != NULL && ads->socket_status > ADS_CONNECTED) { + const char save_error = errno; + ads->ad->on_io_failure(ads->opaque, ads->ad, save_error); + errno = save_error; + } + } + + return sent; +} + +static int +_android_dev_socket_recv(AndroidDevSocket* ads, char* buf, int bufsize) +{ + int recvd = 0; + + /* Make sure that socket is connected. */ + if (ads->socket_status < ADS_CONNECTED) { + D("Attempt to receive from disconnected channel '%s'@%d", + _ads_id_str(ads), _ads_port(ads)); + errno = ECONNRESET; + return -1; + } + + iolooper_add_read(_ads_io_looper(ads), ads->fd); + do { + int res = socket_recv(ads->fd, buf + recvd, bufsize - recvd); + if (res == 0) { + /* Disconnection. */ + errno = ECONNRESET; + recvd = -1; + break; + } + + if (res < 0) { + if (errno == EINTR) { + /* loop on EINTR */ + continue; + } + + if (errno == EWOULDBLOCK || errno == EAGAIN) { + res = iolooper_wait_absolute(_ads_io_looper(ads), ads->deadline); + if (res > 0) { + /* Ready to read. */ + continue; + } + } + recvd = -1; + break; + } + recvd += res; + } while (recvd < bufsize); + iolooper_del_read(_ads_io_looper(ads), ads->fd); + + /* In case of an I/O failure we have to invoke failure callback. Note that we + * report I/O failures only on registered sockets. */ + if (recvd < 0) { + D("I/O error while receiving from channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + + if (ads->ad->on_io_failure != NULL && ads->socket_status > ADS_CONNECTED) { + const char save_error = errno; + ads->ad->on_io_failure(ads->opaque, ads->ad, save_error); + errno = save_error; + } + } + + return recvd; +} + +static int +_android_dev_socket_read_string(AndroidDevSocket* ads, char* str, int strsize) +{ + int n; + + /* Char by char read from the socket, until zero-terminator is read. */ + for (n = 0; n < strsize; n++) { + if (_android_dev_socket_recv(ads, str + n, 1) > 0) { + if (str[n] == '\0') { + /* Done. */ + return n + 1; /* Including zero-terminator. */ + } + } else { + /* I/O error. */ + return -1; + } + } + + /* Buffer was too small. Report that by setting errno to ENOMEM. */ + D("Buffer %d is too small to receive a string from channel '%s'@%d", + strsize, _ads_id_str(ads), _ads_port(ads)); + errno = ENOMEM; + return -1; +} + +static int +_android_dev_socket_read_response(AndroidDevSocket* ads, char* data, int datasize) +{ + int n, res; + int success = 0; + int failure = 0; + int bad_format = 0; + char ok[4]; + + *data = '\0'; + + /* Char by char read from the socket, until ok/ko is read. */ + for (n = 0; n < 2; n++) { + res = _android_dev_socket_recv(ads, ok + n, 1); + if (res > 0) { + if (ok[n] == '\0') { + /* EOS is unexpected here! */ + D("Bad query reply format on channel '%s'@%d: '%s' is too short.", + _ads_id_str(ads), _ads_port(ads), ok); + errno = EINVAL; + return -1; + } + } else { + /* I/O error. */ + return -1; + } + } + + /* Next character must be either ':', or '\0' */ + res = _android_dev_socket_recv(ads, ok + n, 1); + if (res <= 0) { + /* I/O error. */ + return -1; + } + + /* + * Verify format. + */ + + /* Check ok / ko */ + success = memcmp(ok, "ok", 2) == 0; + failure = memcmp(ok, "ko", 2) == 0; + + /* Check the prefix: 'ok'|'ko' & ':'|'\0' */ + if ((success || failure) && (ok[n] == '\0' || ok[n] == ':')) { + /* Format is good. */ + if (ok[n] == '\0') { + /* We're done: no extra data in response. */ + errno = 0; + return success ? 0 : -1; + } + /* Reset buffer offset, so we will start to read the remaining query + * data to the beginning of the supplied buffer. */ + n = 0; + } else { + /* Bad format. Lets move what we've read to the main buffer, and + * continue filling it in. */ + bad_format = 1; + n++; + memcpy(data, ok, n); + } + + /* Read the remainder of reply to the supplied data buffer. */ + res = _android_dev_socket_read_string(ads, data + n, datasize - n); + if (res < 0) { + return res; + } + + /* Lets see if format was bad */ + if (bad_format) { + D("Bad query reply format on channel '%s'@%d: %s.", + _ads_id_str(ads), _ads_port(ads), data); + errno = EINVAL; + return -1; + } else { + errno = 0; + return success ? n : -1; + } +} + +/******************************************************************************** + * Query socket declarations + *******************************************************************************/ + +/* Initializes query socket descriptor. + * Param: + * adsquery - Socket descriptor to initialize. + * opaque - An opaque pointer to associate with the socket. Typicaly it's the + * same pointer that is associated with AndroidDevice instance. + * ad - Android device descriptor that owns the socket. + * port - TCP socket port. + */ +static int _android_query_socket_init(AndroidQuerySocket* adsquery, + void* opaque, + AndroidDevice* ad, + int port); + +/* Destroys query socket descriptor. */ +static void _android_query_socket_destroy(AndroidQuerySocket* adsquery); + +/* Synchronously connects the query socket, and registers it with the server. + * Param: + * adsquery - Descriptor for the query socket to connect. Must have 'deadline' + * field properly setup. + * cb - Callback to invoke when socket connection is completed. Can be NULL. + * Return: + * Zero on success, or non-zero on failure. + */ +static int _android_query_socket_connect(AndroidQuerySocket* adsquery); + +/* Disconnects the query socket. */ +static void _android_query_socket_disconnect(AndroidQuerySocket* adsquery); + +/******************************************************************************** + * Query socket implementation + *******************************************************************************/ + +static int +_android_query_socket_init(AndroidQuerySocket* adsquery, + void* opaque, + AndroidDevice* ad, + int port) +{ + return _android_dev_socket_init(&adsquery->dev_socket, opaque, ad, port, + ADS_TYPE_QUERY); +} + +static void +_android_query_socket_destroy(AndroidQuerySocket* adsquery) +{ + _android_query_socket_disconnect(adsquery); + _android_dev_socket_destroy(&adsquery->dev_socket); +} + +static int +_android_query_socket_connect(AndroidQuerySocket* adsquery) +{ + return _android_dev_socket_connect(&adsquery->dev_socket); +} + +static void +_android_query_socket_disconnect(AndroidQuerySocket* adsquery) +{ + _android_dev_socket_disconnect(&adsquery->dev_socket); +} + +/******************************************************************************** + * Events socket declarations + *******************************************************************************/ + +/* Initializes event socket descriptor. + * Param: + * adsevent - Socket descriptor to initialize. + * opaque - An opaque pointer to associate with the socket. Typicaly it's the + * same pointer that is associated with AndroidDevice instance. + * ad - Android device descriptor that owns the socket. + * port - TCP socket port. + */ +static int _android_event_socket_init(AndroidEventSocket* adsevent, + void* opaque, + AndroidDevice* ad, + int port); + +/* Destroys the event socket descriptor. */ +static void _android_event_socket_destroy(AndroidEventSocket* adsevent); + +/* Synchronously connects event socket. + * Param: + * adsevent - Descriptor for the event socket to connect. Must have 'deadline' + * field properly setup. + * Return: + * Zero on success, or non-zero on failure. + */ +static int _android_event_socket_connect_sync(AndroidEventSocket* adsevent); + +/* Initiates asynchronous event socket connection. + * Param: + * adsevent - Descriptor for the event socket to connect. Must have 'deadline' + * field properly setup. + * cb - Callback to invoke when socket connection is completed. Can be NULL. + * Return: + * Zero on success, or non-zero on failure. + */ +static int _android_event_socket_connect_async(AndroidEventSocket* adsevent, + ads_socket_connected_cb cb); + +/* Disconnects the event socket. */ +static void _android_event_socket_disconnect(AndroidEventSocket* adsevent); + +/* Initiates listening on the event socket. + * Param: + * adsevent - Descriptor for the event socket to listen on. + * str, strsize - Buffer where to read the string. + * cb - A callback to call when the event string is read. Can be NULL. + * Return: + * Zero on success, or non-zero on failure. + */ +static int _android_event_socket_listen(AndroidEventSocket* adsevent, + char* str, + int strsize, + event_cb cb); + +/* Event socket's asynchronous I/O looper callback. + * Param: + * opaque - AndroidEventSocket instance. + * fd - Socket's FD. + * events - I/O type bitsmask (read | write). + */ +static void _on_event_socket_io(void* opaque, int fd, unsigned events); + +/* Callback that is invoked when asynchronous event socket connection is + * completed. */ +static void _on_event_socket_connected(AndroidEventSocket* adsevent, int failure); + +/* Callback that is invoked when an event is received from the device. */ +static void _on_event_received(AndroidEventSocket* adsevent); + +/* Gets I/O looper for asynchronous I/O on event socket. */ +AINLINED Looper* +_aes_looper(AndroidEventSocket* adsevent) +{ + return adsevent->dev_socket.ad->looper; +} + +/******************************************************************************** + * Events socket implementation + *******************************************************************************/ + +static int +_android_event_socket_init(AndroidEventSocket* adsevent, + void* opaque, + AndroidDevice* ad, + int port) +{ + return _android_dev_socket_init(&adsevent->dev_socket, opaque, ad, port, + ADS_TYPE_EVENT); +} + +static void +_android_event_socket_destroy(AndroidEventSocket* adsevent) +{ + _android_event_socket_disconnect(adsevent); + _android_dev_socket_destroy(&adsevent->dev_socket); +} + + +static int +_android_event_socket_connect_sync(AndroidEventSocket* adsevent) +{ + AndroidDevSocket* ads = &adsevent->dev_socket; + const int res = _android_dev_socket_connect(&adsevent->dev_socket); + if (res == 0) { + /* Prepare for async I/O on the event socket. */ + loopIo_init(adsevent->io, _aes_looper(adsevent), ads->fd, + _on_event_socket_io, adsevent); + } + return res; +} + +static int +_android_event_socket_connect_async(AndroidEventSocket* adsevent, + ads_socket_connected_cb cb) +{ + AsyncStatus status; + AndroidDevSocket* ads = &adsevent->dev_socket; + + /* Create asynchronous socket. */ + ads->fd = socket_create_inet(SOCKET_STREAM); + if (ads->fd < 0) { + D("Unable to create socket for channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + if (cb != NULL) { + cb(ads->opaque, ads, errno); + } + return -1; + } + socket_set_nonblock(ads->fd); + + /* Prepare for async I/O on the event socket. */ + loopIo_init(adsevent->io, _aes_looper(adsevent), ads->fd, + _on_event_socket_io, adsevent); + + /* Try to connect. */ + ads->socket_status = ADS_CONNECTING; + adsevent->on_connected = cb; + status = asyncConnector_init(adsevent->connector, &ads->address, adsevent->io); + switch (status) { + case ASYNC_COMPLETE: + /* We're connected to the device socket. */ + ads->socket_status = ADS_CONNECTED; + _on_event_socket_connected(adsevent, 0); + break; + case ASYNC_ERROR: + _on_event_socket_connected(adsevent, errno); + break; + case ASYNC_NEED_MORE: + /* Attempt to connect would block, so connection competion is + * delegates to the looper's I/O routine. */ + default: + break; + } + + return 0; +} + +static void +_android_event_socket_disconnect(AndroidEventSocket* adsevent) +{ + AndroidDevSocket* ads = &adsevent->dev_socket; + + if (ads->socket_status != ADS_DISCONNECTED) { + /* Stop all async I/O. */ + loopIo_done(adsevent->io); + + /* Disconnect common socket. */ + _android_dev_socket_disconnect(ads); + } +} + +static int +_android_event_socket_listen(AndroidEventSocket* adsevent, + char* str, + int strsize, + event_cb cb) +{ + AsyncStatus status; + AndroidDevSocket* ads = &adsevent->dev_socket; + + /* Make sure that device is connected. */ + if (ads->socket_status < ADS_CONNECTED) { + D("Attempt to listen on a disconnected channel '%s'@%d", + _ads_id_str(ads), _ads_port(ads)); + errno = ECONNRESET; + return -1; + } + + /* NOTE: only one reader at any given time! */ + adsevent->on_event = cb; + asyncLineReader_init(&adsevent->alr, str, strsize, adsevent->io); + /* Default EOL for the line reader was '\n'. */ + asyncLineReader_setEOL(&adsevent->alr, '\0'); + status = asyncLineReader_read(&adsevent->alr); + if (status == ASYNC_COMPLETE) { + /* Data has been transferred immediately. Do the callback here. */ + _on_event_received(adsevent); + } else if (status == ASYNC_ERROR) { + D("Error while listening on channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + /* There is one special failure here, when buffer was too small to + * contain the entire string. This is not an I/O, but rather a + * protocol error. So we don't report it to the I/O failure + * callback. */ + if (errno == ENOMEM) { + _on_event_received(adsevent); + } else { + if (ads->ad->on_io_failure != NULL) { + ads->ad->on_io_failure(ads->ad->opaque, ads->ad, errno); + } + } + return -1; + } + return 0; +} + +static void +_on_event_socket_io(void* opaque, int fd, unsigned events) +{ + AsyncStatus status; + AndroidEventSocket* adsevent = (AndroidEventSocket*)opaque; + AndroidDevSocket* ads = &adsevent->dev_socket; + + /* Lets see if we're still wating on a connection to occur. */ + if (ads->socket_status == ADS_CONNECTING) { + /* Complete socket connection. */ + status = asyncConnector_run(adsevent->connector); + if (status == ASYNC_COMPLETE) { + /* We're connected to the device socket. */ + ads->socket_status = ADS_CONNECTED; + D("Channel '%s'@%d is connected asynchronously", + _ads_id_str(ads), _ads_port(ads)); + _on_event_socket_connected(adsevent, 0); + } else if (status == ASYNC_ERROR) { + _on_event_socket_connected(adsevent, adsevent->connector->error); + } + return; + } + + /* + * Device is connected. Continue with the data transfer. We don't expect + * any writes here, since we always write to the event socket synchronously. + */ + + if ((events & LOOP_IO_READ) != 0) { + /* Continue reading data. */ + status = asyncLineReader_read(&adsevent->alr); + if (status == ASYNC_COMPLETE) { + _on_event_received(adsevent); + } else if (status == ASYNC_ERROR) { + D("I/O failure while reading from channel '%s'@%d: %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + /* There is one special failure here, when buffer was too small to + * contain the entire string. This is not an I/O, but rather a + * protocol error. So we don't report it to the I/O failure + * callback. */ + if (errno == ENOMEM) { + _on_event_received(adsevent); + } else { + if (ads->ad->on_io_failure != NULL) { + ads->ad->on_io_failure(ads->ad->opaque, ads->ad, errno); + } + } + } + } +} + +static void +_on_event_socket_connected(AndroidEventSocket* adsevent, int failure) +{ + int res; + AndroidDevSocket* ads = &adsevent->dev_socket; + + if (failure) { + _android_event_socket_disconnect(adsevent); + if (adsevent->on_connected != NULL) { + adsevent->on_connected(ads->opaque, ads, failure); + } + return; + } + + /* Complete event socket connection by identifying it as "event" socket with + * the application. */ + res = _android_dev_socket_register(ads); + if (res) { + const int save_error = errno; + _android_event_socket_disconnect(adsevent); + errno = save_error; + } + + /* Notify callback about connection completion. */ + if (adsevent->on_connected != NULL) { + if (res) { + adsevent->on_connected(ads->opaque, ads, errno); + } else { + adsevent->on_connected(ads->opaque, ads, 0); + } + } +} + +static void +_on_event_received(AndroidEventSocket* adsevent) +{ + if (adsevent->on_event != NULL) { + AndroidDevice* ad = adsevent->dev_socket.ad; + adsevent->on_event(ad->opaque, ad, (char*)adsevent->alr.buffer, + adsevent->alr.pos); + } +} + +/******************************************************************************** + * Android device connection + *******************************************************************************/ + +/* Callback that is invoked when event socket is connected and registered as part + * of the _android_device_connect_async API. + * Param: + * opaque - Opaque pointer associated with AndroidDevice instance. + * ads - Common socket descriptor for the event socket. + * failure - If zero connection has succeeded, otherwise contains 'errno'-reason + * for connection failure. + */ +static void +_on_android_device_connected_async(void* opaque, + AndroidDevSocket* ads, + int failure) +{ + int res; + AndroidDevice* ad = ads->ad; + + if (failure) { + /* Depending on the failure code we will either retry, or bail out. */ + switch (failure) { + case EPIPE: + case EAGAIN: + case EINPROGRESS: + case EALREADY: + case EHOSTUNREACH: + case EHOSTDOWN: + case ECONNREFUSED: + case ESHUTDOWN: + case ENOTCONN: + case ECONNRESET: + case ECONNABORTED: + case ENETRESET: + case ENETUNREACH: + case ENETDOWN: + case EBUSY: +#ifndef _DARWIN_C_SOURCE + case ERESTART: + case ECOMM: + case ENONET: +#endif /* _DARWIN_C_SOURCE */ + /* Device is not available / reachable at the moment. + * Retry connection later. */ + loopTimer_startRelative(ad->timer, ADS_RETRY_CONNECTION_TIMEOUT); + return; + default: + D("Failed to asynchronously connect channel '%s':%d %s", + _ads_id_str(ads), _ads_port(ads), strerror(errno)); + if (ad->on_connected != NULL) { + ad->on_connected(ad->opaque, ad, failure); + } + break; + } + return; + } + + /* Event socket is connected. Connect the query socket now. Give it 5 + * seconds to connect. */ + _ads_set_deadline(&ad->query_socket.dev_socket, 5000); + res = _android_query_socket_connect(&ad->query_socket); + if (res == 0) { + /* Query socket is connected. */ + if (ad->on_connected != NULL) { + ad->on_connected(ad->opaque, ad, 0); + } + } else { + /* If connection completion has failed - disconnect the sockets. */ + _android_event_socket_disconnect(&ad->event_socket); + _android_query_socket_disconnect(&ad->query_socket); + + if (ad->on_connected != NULL) { + ad->on_connected(ad->opaque, ad, errno); + } + } +} + +static void +_on_timer(void* opaque) +{ + /* Retry the connection. */ + AndroidDevice* ad = (AndroidDevice*)opaque; + android_device_connect_async(ad, ad->on_connected); +} + +/* Destroys and frees the descriptor. */ +static void +_android_device_free(AndroidDevice* ad) +{ + if (ad != NULL) { + _android_event_socket_destroy(&ad->event_socket); + _android_query_socket_destroy(&ad->query_socket); + + /* Delete asynchronous I/O looper. */ + if (ad->looper != NULL ) { + loopTimer_done(ad->timer); + looper_free(ad->looper); + } + + /* Delete synchronous I/O looper. */ + if (ad->io_looper != NULL) { + iolooper_reset(ad->io_looper); + iolooper_free(ad->io_looper); + } + + AFREE(ad); + } +} + +/******************************************************************************** + * Android device API + *******************************************************************************/ + +AndroidDevice* +android_device_init(void* opaque, int port, io_failure_cb on_io_failure) +{ + int res; + AndroidDevice* ad; + + ANEW0(ad); + + ad->opaque = opaque; + ad->on_io_failure = on_io_failure; + + /* Create I/O looper for synchronous I/O on the device. */ + ad->io_looper = iolooper_new(); + if (ad->io_looper == NULL) { + E("Unable to create synchronous I/O looper for android device."); + _android_device_free(ad); + return NULL; + } + + /* Create a looper for asynchronous I/O on the device. */ + ad->looper = looper_newCore(); + if (ad->looper != NULL) { + /* Create a timer that will be used for connection retries. */ + loopTimer_init(ad->timer, ad->looper, _on_timer, ad); + } else { + E("Unable to create asynchronous I/O looper for android device."); + _android_device_free(ad); + return NULL; + } + + /* Init query socket. */ + res = _android_query_socket_init(&ad->query_socket, opaque, ad, port); + if (res) { + _android_device_free(ad); + return NULL; + } + + /* Init event socket. */ + res = _android_event_socket_init(&ad->event_socket, opaque, ad, port); + if (res) { + _android_device_free(ad); + return NULL; + } + + return ad; +} + +void +android_device_destroy(AndroidDevice* ad) +{ + if (ad != NULL) { + _android_device_free(ad); + } +} + +int +android_device_connect_sync(AndroidDevice* ad, int to) +{ + int res; + + /* Setup deadline for the connections. */ + _ads_set_deadline(&ad->query_socket.dev_socket, to); + ad->event_socket.dev_socket.deadline = ad->query_socket.dev_socket.deadline; + + /* Connect the query socket first. */ + res = _android_query_socket_connect(&ad->query_socket); + if (!res) { + /* Connect to the event socket next. */ + res = _android_event_socket_connect_sync(&ad->event_socket); + } + + return res; +} + +int +android_device_connect_async(AndroidDevice* ad, device_connected_cb on_connected) +{ + /* No deadline for async connections. */ + ad->query_socket.dev_socket.deadline = DURATION_INFINITE; + ad->event_socket.dev_socket.deadline = DURATION_INFINITE; + + /* Connect to the event socket first, and delegate query socket connection + * into callback invoked when event socket is connected. NOTE: In case of + * failure 'on_connected' callback has already been called from + * _on_android_device_connected_async routine. */ + ad->on_connected = on_connected; + return _android_event_socket_connect_async(&ad->event_socket, + _on_android_device_connected_async); +} + +void +android_device_disconnect(AndroidDevice* ad) +{ + _android_event_socket_disconnect(&ad->event_socket); + _android_query_socket_disconnect(&ad->query_socket); +} + +int +android_device_query(AndroidDevice* ad, + const char* query, + char* buff, + size_t buffsize, + int to) +{ + int res; + + /* Setup deadline for the query. */ + _ads_set_deadline(&ad->query_socket.dev_socket, to); + + /* Send the query. */ + res = _android_dev_socket_send(&ad->query_socket.dev_socket, query, + strlen(query) + 1); + if (res > 0) { + /* Receive the response. */ + res = _android_dev_socket_read_response(&ad->query_socket.dev_socket, + buff, buffsize); + return (res >= 0) ? 0 : -1; + } + + return -1; +} + +int +android_device_listen(AndroidDevice* ad, + char* buff, + int buffsize, + event_cb on_event) +{ + return _android_event_socket_listen(&ad->event_socket, buff, buffsize, + on_event); +} diff --git a/android/android-device.h b/android/android-device.h new file mode 100644 index 0000000..9fde906 --- /dev/null +++ b/android/android-device.h @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef ANDROID_ANDROID_DEVICE_H_ +#define ANDROID_ANDROID_DEVICE_H_ + +/* + * Encapsulates an exchange protocol between the emulator, and an Android device + * that is connected to the host via USB. The communication is established over + * a TCP port forwarding, enabled by ADB (always use 'adb -d forward ...' variant + * of this command, so ADB will know to enable port forwarding on the connected + * device, and not on the emulator's guest system). + * + * Exchange protocol contains two channel: + * + * - Query channel. + * - Event channel. + * + * Both channels are implemented on top of TCP sockets that are connected to the + * same port. + * + * I QUERY CHANNEL. + * Query channel is intended to send queries to and receive responses from the + * connected device. It is implemented on top of iolooper_xxx API (see iolooper.h) + * because it must work outside of the main event loop. This is required to enable + * proper initialization of components (such as sensors) that must be set up + * before emulator enters the main loop. + * + * II EVENT CHANNEL. + * Event channel is intended to listen on events sent from the device, and + * asynchronously report them back to the client of this API by invoking an event + * callback that was registered by the client. Event channel is implemented on + * top of asyncXxx API (see android/async-utils.*). Note that using of asyncXxx + * API limits the use of event channel to the time after the emulator has entered + * its main event loop. The only exception is if event channel is connected from + * android_device_connect_sync API, in which case iolooper_xxx API is used to + * establish the connection. However, even in this case listening for events will + * not be available until after the emulator enters its event loop, since event + * listening always uses asyncXxx API. + * + * III. ESTABLISHING CONNECTION. + * ADB port forwarding requires that the server socket is to be run on the device, + * while emulator must use a client socket for communication. Thus, it's the + * emulator that initiates the connection. + * + * There are two ways how emulator can initiate the connection: + * + * - Synchronous connection. + * - Asynchronous connection. + * + * III.I SYNCHROUNOUS CONNECTION. + * Synchronous connection is initiated via android_device_connect_sync API, and + * completes synchronously. + * + * This API should be used when connection with the device is required at the time + * of the call. For instance, when initializing sensor emulation, connection with + * the device is required to properly set up the emulator before the guest system + * starts, and before emulator enters its main event loop. + * + * III.II ASYNCHRONOUS CONNECTION. + * Asynchronous connection is initiated via android_device_connect_async API. The + * main difference with the synchronous connection is that this API will not fail + * if connection is not immediately available. If connection is not available at + * the time of the call, the API will schedule a retry (based on a timer), and + * will continue reprying untill connection becomes available, or until an error + * occurs that prevent further retries. + * + * This API should be used when ... Well, whenever appropriate. For instance, + * sensor emulation will use this API to restore lost connection with the device. + * + * NOTE: Asynchronous connection will complete no sooner than the emulator enters + * its main loop. + * + * IV EXCHANGE PROTOCOL. + * Obviously, there must be some application running on the device, that implements + * a socket server listening on the forwarded TCP port, and accepting the clients. + * + * IV.I Query vs. event channel. + * The exchange protocol assumes, that when a channel is connected, it will + * identify itself by sending a string containing channel type. Only after such + * identification has been made the channel becomes available for use. + * + * IV.II Message format. + * All data that is transferred in both directions over both channels are zero- + * terminated strings. + */ + +#include "qemu-common.h" +#include "android/async-utils.h" +#include "android/utils/debug.h" + +/* TCP port reserved for sensor emulation. */ +#define AD_SENSOR_PORT 1968 + +/* Definis infinite timeout. */ +#define AD_INFINITE_WAIT -1 + +/* Android device descriptor. */ +typedef struct AndroidDevice AndroidDevice; + +/******************************************************************************** + * Callback declarations + *******************************************************************************/ + +/* Callback routine that is invoked when android device is connected, or failed + * to connect. As discussed above, this callback is called when both, query and + * event channels have been connected. This callback is used only for asynchronous + * connections. + * Param: + * opaque - Opaque pointer that was passed to android_device_init API. + * ad - Androd device descriptor for the connection. + * failure - Zero indicates that connection with the device has been successfuly + * established. Non-zero vaule passed in this parameter indicates a failure, + * and contains 'errno'-reason for failure. + */ +typedef void (*device_connected_cb)(void* opaque, AndroidDevice* ad, int failure); + +/* Callback routine that is invoked on an event received in the event channel. + * NOTE: It's important to check 'errno' in this callback. If 'errno' is set to + * ENOMEM, this signals that buffer passed to android_device_listen was too small + * to contain the entire event message. + * Param: + * opaque - Opaque pointer that was passed to android_device_init API. + * ad - Androd device descriptor for the connection. + * msg - Event message (a zero-terminated string) received from the device. + * msgsize - Event message size (including zero-terminator). + */ +typedef void (*event_cb)(void* opaque, AndroidDevice* ad, char* msg, int msgsize); + +/* Callback routine that is invoked when an I/O failure occurs on a channel. + * Note that this callback will not be invoked on connection failures. + * Param: + * opaque - Opaque pointer that was passed to android_device_init API. + * ad - Android device instance + * failure - Contains 'errno' indicating the reason for failure. + */ +typedef void (*io_failure_cb)(void* opaque, AndroidDevice* ad, int failure); + +/******************************************************************************** + * Android Device API. + *******************************************************************************/ + +/* Initializes android device descriptor. + * Param: + * opaque - An opaque pointer to associate with the descriptor. This pointer + * will be passed to all callbacks (see above) that were invoked by the + * initializing android device instance. + * port - TCP port to use for connection. + * on_io_failure - Callback to invoke when an I/O failure occurs on a channel + * used by the initializing android device instance. Can be NULL. + * Return: + * Initialized android device descriptor on success, or NULL on failure. + */ +extern AndroidDevice* android_device_init(void* opaque, + int port, + io_failure_cb on_io_failure); + +/* Disconnects and destroys android device descriptor. + * Param: + * ad - Android device descriptor, returned from android_device_init API. + * Note that memory allocated for this descriptor will be freed in this + * routine. + */ +extern void android_device_destroy(AndroidDevice* ad); + +/* Synchronously connects to the device. See notes above for more details. + * Param: + * ad - Android device descriptor, returned from android_device_init API. + * to - Milliseconds to wait for connection to be established. + * Return: + * Zero on success, or non-zero value on failure with 'errno' properly set. + */ +extern int android_device_connect_sync(AndroidDevice* ad, int to); + +/* Asynchronously connects to the device. See notes above for more details. + * Param: + * ad - Android device descriptor, returned from android_device_init API. + * on_connected - Callback to invoke when connection is completed (i,e, both, + * event, and query channels have been connected). This parameter can be + * NULL. Note that connection errors will be also reported through this + * callback. Also note that this callback will be invoked even if this + * routine returns with a failure. + * Return: + * Zero on success, or non-zero value on failure with 'errno' properly set. + */ +extern int android_device_connect_async(AndroidDevice* ad, + device_connected_cb on_connected); + +/* Disconnects from the android device. + * Param: + * ad - Android device descriptor, returned from android_device_init API. + */ +extern void android_device_disconnect(AndroidDevice* ad); + +/* Queries the device via query channel. + * Param: + * ad - Android device descriptor, returned from android_device_init API. + * query - Zero-terminated query string. + * buff, buffsize - Buffer where to receive the response to the query. + * to - Milliseconds to wait for the entire query to complete. + * Return: + * Zero on success, or non-zero value on failure with 'errno' properly set: + * - 0 Indicates that the server has failed the query. + * - Anything else indicates an I/O error. + */ +extern int android_device_query(AndroidDevice* ad, + const char* query, + char* buff, + size_t buffsize, + int to); + +/* Start listening on the event channel. + * Param: + * ad - Android device descriptor, returned from android_device_init API. + * buff, buffsize - Buffer where to receive the event message. + * on_event - Callback to invoke on event. Note that this callback will be + * invoked even if this routine returns with a failure. + * Return: + * Zero on success, or non-zero value on failure with 'errno' properly set. + */ +extern int android_device_listen(AndroidDevice* ad, + char* buff, + int buffsize, + event_cb on_event); + +#endif /* ANDROID_ANDROID_DEVICE_H_ */ diff --git a/android/async-utils.c b/android/async-utils.c index 35a144a..e410de0 100644 --- a/android/async-utils.c +++ b/android/async-utils.c @@ -25,6 +25,7 @@ asyncReader_init(AsyncReader* ar, ar->buffer = buffer; ar->buffsize = buffsize; ar->pos = 0; + ar->io = io; if (buffsize > 0) loopIo_wantRead(io); } @@ -71,6 +72,7 @@ asyncWriter_init(AsyncWriter* aw, aw->buffer = buffer; aw->buffsize = buffsize; aw->pos = 0; + aw->io = io; if (buffsize > 0) loopIo_wantWrite(io); } @@ -118,6 +120,7 @@ asyncLineReader_init(AsyncLineReader* alr, alr->buffsize = buffsize; alr->pos = 0; alr->io = io; + alr->eol = '\n'; if (buffsize > 0) loopIo_wantRead(io); } @@ -150,7 +153,7 @@ asyncLineReader_read(AsyncLineReader* alr) return ASYNC_ERROR; } alr->buffer[alr->pos++] = (uint8_t)ch; - if (ch == '\n') { + if (ch == alr->eol) { loopIo_dontWantRead(alr->io); return ASYNC_COMPLETE; } @@ -187,14 +190,16 @@ asyncLineReader_getLine(AsyncLineReader* alr) pos--; /* Check that we have a proper terminator, and replace it with 0 */ - if (buffer[pos] != '\n') - return NULL; - - buffer[pos] = '\0'; + if (alr->eol == '\n') { + if (buffer[pos] != '\n') + return NULL; - /* Also strip \r\n */ - if (pos > 0 && buffer[--pos] == '\r') { buffer[pos] = '\0'; + + /* Also strip \r\n */ + if (pos > 0 && buffer[--pos] == '\r') { + buffer[pos] = '\0'; + } } return (const char*) buffer; diff --git a/android/async-utils.h b/android/async-utils.h index 93148c1..6d460c2 100644 --- a/android/async-utils.h +++ b/android/async-utils.h @@ -137,6 +137,7 @@ typedef struct { size_t buffsize; size_t pos; LoopIo* io; + char eol; } AsyncLineReader; /* Setup an AsyncLineReader to read at most 'buffsize' characters (bytes) @@ -155,6 +156,17 @@ void asyncLineReader_init(AsyncLineReader* alr, size_t buffsize, LoopIo* io); +/* Sets line terminator character for the reader. + * By default, asyncLineReader_init will set EOL to be '\n'. Sometimes it's more + * convenient to have '\0' as line terminator, so "line" reader easily becomes + * a "string" reader. + */ +AINLINED void +asyncLineReader_setEOL(AsyncLineReader* alr, char eol) +{ + alr->eol = eol; +} + /* Try to read line characters from 'io'. * Returns: * ASYNC_COMPLETE: An end-of-line was detected, call asyncLineReader_getLine diff --git a/android/avd/hardware-properties.ini b/android/avd/hardware-properties.ini index a2b05df..83a9300 100644 --- a/android/avd/hardware-properties.ini +++ b/android/avd/hardware-properties.ini @@ -367,6 +367,27 @@ default = yes abstract = Proximity support description = Whether there is an proximity in the device. +# Magnetic field sensor +name = hw.sensors.magnetic_field +type = boolean +default = yes +abstract = Magnetic field support +description = Provides magnetic field senosr values. + +# Orientation sensor +name = hw.sensors.orientation +type = boolean +default = yes +abstract = Orientation support +description = Provides orientation sensor values. + +# Temperature sensor +name = hw.sensors.temperature +type = boolean +default = yes +abstract = Temperature support +description = Provides temperatore sensor values. + # Kernel image. # # kernel.path specified the path to the kernel image diff --git a/android/hw-sensors.c b/android/hw-sensors.c index 9c39196..6a7721c 100644 --- a/android/hw-sensors.c +++ b/android/hw-sensors.c @@ -20,7 +20,10 @@ #include "hw/hw.h" #include "qemu-char.h" #include "qemu-timer.h" +#include "android/sensors-port.h" +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) #define D(...) VERBOSE_PRINT(sensors,__VA_ARGS__) /* define T_ACTIVE to 1 to debug transport communications */ @@ -165,9 +168,10 @@ typedef struct { typedef struct HwSensorClient HwSensorClient; typedef struct { - QemudService* service; - Sensor sensors[MAX_SENSORS]; - HwSensorClient* clients; + QemudService* service; + Sensor sensors[MAX_SENSORS]; + HwSensorClient* clients; + AndroidSensorsPort* sensors_port; } HwSensors; struct HwSensorClient { @@ -301,7 +305,8 @@ _hwSensorClient_tick( void* opaque ) if (_hwSensorClient_enabled(cl, ANDROID_SENSOR_MAGNETIC_FIELD)) { sensor = &hw->sensors[ANDROID_SENSOR_MAGNETIC_FIELD]; - snprintf(buffer, sizeof buffer, "magnetic-field:%g:%g:%g", + /* NOTE: sensors HAL expects "magnetic", not "magnetic-field" name here. */ + snprintf(buffer, sizeof buffer, "magnetic:%g:%g:%g", sensor->u.magnetic.x, sensor->u.magnetic.y, sensor->u.magnetic.z); @@ -430,6 +435,16 @@ _hwSensorClient_receive( HwSensorClient* cl, uint8_t* msg, int msglen ) D("%s: %s %s sensor", __FUNCTION__, (cl->enabledMask & (1 << id)) ? "enabling" : "disabling", msg); } + + /* If emulating device is connected update sensor state there too. */ + if (hw->sensors_port != NULL && sensors_port_is_connected(hw->sensors_port)) { + if (enabled) { + sensors_port_enable_sensor(hw->sensors_port, (const char*)msg); + } else { + sensors_port_disable_sensor(hw->sensors_port, (const char*)msg); + } + } + _hwSensorClient_tick(cl); return; } @@ -645,14 +660,40 @@ _hwSensors_setCoarseOrientation( HwSensors* h, AndroidCoarseOrientation orient static void _hwSensors_init( HwSensors* h ) { + /* Try to see if there is a device attached that can be used for + * sensor emulation. */ + h->sensors_port = sensors_port_create(h); + if (h->sensors_port == NULL) { + W("Unable to create sensors port: %s", strerror(errno)); + } + h->service = qemud_service_register("sensors", 0, h, _hwSensors_connect, _hwSensors_save, _hwSensors_load); - if (android_hw->hw_accelerometer) + if (android_hw->hw_accelerometer) { h->sensors[ANDROID_SENSOR_ACCELERATION].enabled = 1; + } - if (android_hw->hw_sensors_proximity) + if (android_hw->hw_sensors_proximity) { h->sensors[ANDROID_SENSOR_PROXIMITY].enabled = 1; + } + + if (android_hw->hw_sensors_magnetic_field) { + h->sensors[ANDROID_SENSOR_MAGNETIC_FIELD].enabled = 1; + } + + if (android_hw->hw_sensors_orientation) { + h->sensors[ANDROID_SENSOR_ORIENTATION].enabled = 1; + } + + if (android_hw->hw_sensors_temperature) { + h->sensors[ANDROID_SENSOR_TEMPERATURE].enabled = 1; + } + + if (h->sensors_port != NULL) { + /* Init sensors on the attached device. */ + sensors_port_init_sensors(h->sensors_port); + } /* XXX: TODO: Add other tests when we add the corresponding * properties to hardware-properties.ini et al. */ diff --git a/android/sensors-port.c b/android/sensors-port.c new file mode 100644 index 0000000..7422f96 --- /dev/null +++ b/android/sensors-port.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2011 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/sensors-port.h" +#include "android/hw-sensors.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(sensors_port,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(camera) + +/* Maximum number of sensors supported. */ +#define ASP_MAX_SENSOR 12 + +/* Maximum length of a sensor message. */ +#define ASP_MAX_SENSOR_MSG 1024 + +/* Maximum length of a sensor event. */ +#define ASP_MAX_SENSOR_EVENT 256 + +/* Query timeout in milliseconds. */ +#define ASP_QUERY_TIMEOUT 3000 + +/* Sensors port descriptor. */ +struct AndroidSensorsPort { + /* Caller identifier. */ + void* opaque; + /* Connected android device. */ + AndroidDevice* device; + /* String containing list of all available sensors. */ + char sensors[ASP_MAX_SENSOR * 64]; + /* Array of available sensor names. Note that each string in this array + * points inside the 'sensors' buffer. */ + const char* sensor_list[ASP_MAX_SENSOR]; + /* Number of available sensors. */ + int sensors_num; + /* Connection status: 1 connected, 0 - disconnected. */ + int is_connected; + /* Buffer where to receive sensor messages. */ + char sensor_msg[ASP_MAX_SENSOR_MSG]; + /* Buffer where to receive sensor events. */ + char events[ASP_MAX_SENSOR_EVENT]; +}; + +/* Destroys and frees the descriptor. */ +static void +_sensors_port_free(AndroidSensorsPort* asp) +{ + if (asp != NULL) { + if (asp->device != NULL) { + android_device_destroy(asp->device); + } + AFREE(asp); + } +} + +/******************************************************************************** + * Sensors port callbacks + *******************************************************************************/ + +/* A callback that invoked on sensor events. + * Param: + * opaque - AndroidSensorsPort instance. + * ad - Android device used by this sensors port. + * msg, msgsize - Sensor event message + * failure - Message receiving status. + */ +static void +_on_sensor_received(void* opaque, AndroidDevice* ad, char* msg, int msgsize) +{ + float fvalues[3] = {0, 0, 0}; + char sensor[ASP_MAX_SENSOR_MSG]; + char* value; + int id; + AndroidSensorsPort* asp = (AndroidSensorsPort*)opaque; + + if (errno) { + D("Sensors notification has failed on sensors port: %s", strerror(errno)); + return; + } + + /* Parse notification, separating sensor name from parameters. */ + memcpy(sensor, msg, msgsize); + value = strchr(sensor, ':'); + if (value == NULL) { + W("Bad format for sensor notification: %s", msg); + return; + } + sensor[value-sensor] = '\0'; + value++; + + id = android_sensors_get_id_from_name(sensor); + if (id >= 0) { + /* Parse the value part to get the sensor values(a, b, c) */ + int i; + char* pnext; + char* pend = value + strlen(value); + for (i = 0; i < 3; i++, value = pnext + 1) { + pnext=strchr( value, ':' ); + if (pnext) { + *pnext = 0; + } else { + pnext = pend; + } + + if (pnext > value) { + if (1 != sscanf( value,"%g", &fvalues[i] )) { + W("Bad parameters in sensor notification %s", msg); + return; + } + } + } + android_sensors_set(id, fvalues[0], fvalues[1], fvalues[2]); + } else { + W("Unknown sensor name '%s' in '%s'", sensor, msg); + } + + /* Listen to the next event. */ + android_device_listen(ad, asp->events, sizeof(asp->events), _on_sensor_received); +} + +/* A callback that is invoked when android device is connected (i.e. both, command + * and event channels have been stablished. + * Param: + * opaque - AndroidSensorsPort instance. + * ad - Android device used by this sensors port. + * failure - Connections status. + */ +static void +_on_device_connected(void* opaque, AndroidDevice* ad, int failure) +{ + if (!failure) { + AndroidSensorsPort* asp = (AndroidSensorsPort*)opaque; + asp->is_connected = 1; + D("Sensor emulation has started"); + /* Initialize sensors on device. */ + sensors_port_init_sensors(asp); + } +} + +/* Invoked when an I/O failure occurs on a socket. + * Note that this callback will not be invoked on connection failures. + * Param: + * opaque - AndroidSensorsPort instance. + * ad - Android device instance + * ads - Connection socket where failure has occured. + * failure - Contains 'errno' indicating the reason for failure. + */ +static void +_on_io_failure(void* opaque, AndroidDevice* ad, int failure) +{ + AndroidSensorsPort* asp = (AndroidSensorsPort*)opaque; + E("Sensors port got disconnected: %s", strerror(failure)); + asp->is_connected = false; + android_device_disconnect(ad); + android_device_connect_async(ad, _on_device_connected); +} + +/******************************************************************************** + * Sensors port API + *******************************************************************************/ + +AndroidSensorsPort* +sensors_port_create(void* opaque) +{ + AndroidSensorsPort* asp; + char* wrk; + int res; + + ANEW0(asp); + asp->opaque = opaque; + asp->is_connected = 0; + + asp->device = android_device_init(asp, AD_SENSOR_PORT, _on_io_failure); + if (asp->device == NULL) { + _sensors_port_free(asp); + return NULL; + } + + res = android_device_connect_sync(asp->device, ASP_QUERY_TIMEOUT); + if (res != 0) { + sensors_port_destroy(asp); + return NULL; + } + + res = android_device_query(asp->device, "list", + asp->sensors, sizeof(asp->sensors), + ASP_QUERY_TIMEOUT); + if (res != 0) { + sensors_port_destroy(asp); + return NULL; + } + + /* Parse sensor list. */ + asp->sensors_num = 0; + wrk = asp->sensors; + + while (wrk != NULL && *wrk != '\0' && *wrk != '\n') { + asp->sensor_list[asp->sensors_num] = wrk; + asp->sensors_num++; + wrk = strchr(wrk, '\n'); + if (wrk != NULL) { + *wrk = '\0'; wrk++; + } + } + + android_device_listen(asp->device, asp->events, sizeof(asp->events), + _on_sensor_received); + return asp; +} + +int +sensors_port_init_sensors(AndroidSensorsPort* asp) +{ + int res, id; + + /* Disable all sensors for now. Reenable only those that are emulated. */ + res = sensors_port_disable_sensor(asp, "all"); + if (res) { + return res; + } + + /* Start listening on sensor events. */ + res = android_device_listen(asp->device, asp->events, sizeof(asp->events), + _on_sensor_received); + if (res) { + return res; + } + + /* Walk throuh the list of enabled sensors enabling them on the device. */ + for (id = 0; id < MAX_SENSORS; id++) { + if (android_sensors_get_sensor_status(id) == 1) { + res = sensors_port_enable_sensor(asp, android_sensors_get_name_from_id(id)); + if (res == 0) { + D("Sensor '%s' is enabled on the device.", + android_sensors_get_name_from_id(id)); + } + } + } + + /* Start sensor events. */ + return sensors_port_start(asp); +} + +void +sensors_port_destroy(AndroidSensorsPort* asp) +{ + _sensors_port_free(asp); +} + +int +sensors_port_is_connected(AndroidSensorsPort* asp) +{ + return asp->is_connected; +} + +int +sensors_port_enable_sensor(AndroidSensorsPort* asp, const char* name) +{ + char query[1024]; + char qresp[1024]; + snprintf(query, sizeof(query), "enable:%s", name); + const int res = + android_device_query(asp->device, query, qresp, sizeof(qresp), + ASP_QUERY_TIMEOUT); + if (res) { + if (errno) { + D("Query '%s' failed on I/O: %s", query, strerror(errno)); + } else { + D("Query '%s' failed on device: %s", query, qresp); + } + } + return res; +} + +int +sensors_port_disable_sensor(AndroidSensorsPort* asp, const char* name) +{ + char query[1024]; + char qresp[1024]; + snprintf(query, sizeof(query), "disable:%s", name); + const int res = + android_device_query(asp->device, query, qresp, sizeof(qresp), + ASP_QUERY_TIMEOUT); + if (res) { + if (errno) { + D("Query '%s' failed on I/O: %s", query, strerror(errno)); + } else { + D("Query '%s' failed on device: %s", query, qresp); + } + } + return res; +} + +int +sensors_port_start(AndroidSensorsPort* asp) +{ + char qresp[ASP_MAX_SENSOR_MSG]; + const int res = + android_device_query(asp->device, "start", qresp, sizeof(qresp), + ASP_QUERY_TIMEOUT); + if (res) { + if (errno) { + D("Query 'start' failed on I/O: %s", strerror(errno)); + } else { + D("Query 'start' failed on device: %s", qresp); + } + } + return res; +} + +int +sensors_port_stop(AndroidSensorsPort* asp) +{ + char qresp[ASP_MAX_SENSOR_MSG]; + const int res = + android_device_query(asp->device, "stop", qresp, sizeof(qresp), + ASP_QUERY_TIMEOUT); + if (res) { + if (errno) { + D("Query 'stop' failed on I/O: %s", strerror(errno)); + } else { + D("Query 'stop' failed on device: %s", qresp); + } + } + + return res; +} diff --git a/android/sensors-port.h b/android/sensors-port.h new file mode 100644 index 0000000..dc5c966 --- /dev/null +++ b/android/sensors-port.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef ANDROID_SENSORS_PORT_H_ +#define ANDROID_SENSORS_PORT_H_ + +/* + * Encapsulates exchange protocol between the sensor emulator, and an application + * running on an Android device that provides sensor values, and is connected to + * the host via USB. + */ + +#include "android/android-device.h" + +/* Declares sensors port descriptor. */ +typedef struct AndroidSensorsPort AndroidSensorsPort; + +/* Creates sensors port, and connects it to the device. + * Param: + * opaque - An opaque pointer that is passed back to the callback routines. + * Return: + * Initialized device descriptor on success, or NULL on failure. If failure is + * returned from this routine, 'errno' indicates the reason for failure. If this + * routine successeds, a connection is established with the sensor reading + * application on the device. + */ +extern AndroidSensorsPort* sensors_port_create(void* opaque); + +/* Disconnects from the sensors port, and destroys the descriptor. */ +extern void sensors_port_destroy(AndroidSensorsPort* asp); + +/* Initializes sensors on the connected device. */ +extern int sensors_port_init_sensors(AndroidSensorsPort* asp); + +/* Checks if port is connected to a sensor reading application on the device. + * Note that connection can go out and then be restored at any time after + * sensors_port_create API succeeded. + */ +extern int sensors_port_is_connected(AndroidSensorsPort* asp); + +/* Enables events from a particular sensor. + * Param: + * asp - Android sensors port instance returned from sensors_port_create. + * name - Name of the sensor to enable events on. If this parameter is "all", + * then events on all sensors will be enabled. + * Return: + * Zero on success, failure otherwise. + */ +extern int sensors_port_enable_sensor(AndroidSensorsPort* asp, const char* name); + + +/* Disables events from a particular sensor. + * Param: + * asp - Android sensors port instance returned from sensors_port_create. + * name - Name of the sensor to disable events on. If this parameter is "all", + * then events on all sensors will be disable. + * Return: + * Zero on success, failure otherwise. + */ +extern int sensors_port_disable_sensor(AndroidSensorsPort* asp, const char* name); + +/* Queries the connected application to start delivering sensor events. + * Param: + * asp - Android sensors port instance returned from sensors_port_create. + * Return: + * Zero on success, failure otherwise. + */ +extern int sensors_port_start(AndroidSensorsPort* asp); + +/* Queries the connected application to stop delivering sensor events. + * Param: + * asp - Android sensors port instance returned from sensors_port_create. + * Return: + * Zero on success, failure otherwise. + */ +extern int sensors_port_stop(AndroidSensorsPort* asp); + +#endif /* ANDROID_SENSORS_PORT_H_ */ diff --git a/android/utils/debug.h b/android/utils/debug.h index 096b002..ea5a6b0 100644 --- a/android/utils/debug.h +++ b/android/utils/debug.h @@ -36,6 +36,8 @@ _VERBOSE_TAG(sensors, "emulated sensors") \ _VERBOSE_TAG(memcheck, "memory checker") \ _VERBOSE_TAG(camera, "camera") \ + _VERBOSE_TAG(adevice, "android device connected via port forwarding") \ + _VERBOSE_TAG(sensors_port, "sensors emulator connected to android device") \ _VERBOSE_TAG(gles, "hardware OpenGLES emulation") \ #define _VERBOSE_TAG(x,y) VERBOSE_##x, |