diff options
-rw-r--r-- | CHANGES.TXT | 8 | ||||
-rw-r--r-- | android/avd/info.c | 15 | ||||
-rw-r--r-- | android/hw-qemud.c | 282 | ||||
-rw-r--r-- | android/main.c | 35 | ||||
-rw-r--r-- | docs/ANDROID-QEMUD-SERVICES.TXT | 155 | ||||
-rw-r--r-- | docs/ANDROID-QEMUD.TXT | 18 |
6 files changed, 418 insertions, 95 deletions
diff --git a/CHANGES.TXT b/CHANGES.TXT index 93d3689..d3c301d 100644 --- a/CHANGES.TXT +++ b/CHANGES.TXT @@ -121,6 +121,14 @@ OTHER: - Using '-no-audio' no longer disables sound hardware emulation. It simply mutes the emulator program on the host. +- The window title bar changes when you toggle persistent trackball mode + (F6 by default). It will display something like the following: + + "Press F6 to exit trackball mode ..." + + The actual text depends on your key binding configuration. This is to help + people toggle the mode by accident. + ============================================================================== Changes between 1.6 and 1.7 diff --git a/android/avd/info.c b/android/avd/info.c index ec74249..1bdb59c 100644 --- a/android/avd/info.c +++ b/android/avd/info.c @@ -1281,9 +1281,18 @@ _getBuildSkin( AvdInfo* i, AvdInfoParams* params ) #define PREBUILT_SKINS_DIR "development/emulator/skins" - /* the (current) default skin directory */ - p = bufprint( temp, end, "%s/%s", - i->androidBuildRoot, PREBUILT_SKINS_DIR ); + do { + /* try in <sysdir>/../skins first */ + p = bufprint( temp, end, "%s/../skins", + i->androidBuildRoot ); + if (path_exists(temp)) + break; + + /* the (current) default skin directory */ + p = bufprint( temp, end, "%s/%s", + i->androidBuildRoot, PREBUILT_SKINS_DIR ); + } while (0); + } else { p = bufprint( temp, end, "%s", skinDir ); } diff --git a/android/hw-qemud.c b/android/hw-qemud.c index 25d8717..cc28e63 100644 --- a/android/hw-qemud.c +++ b/android/hw-qemud.c @@ -40,93 +40,19 @@ #define MAX_FRAME_PAYLOAD 65535 +/* define SUPPORT_LEGACY_QEMUD to 1 if you want to support + * talking to a legacy qemud daemon. See docs/ANDROID-QEMUD.TXT + * for details. + */ +#define SUPPORT_LEGACY_QEMUD 1 + /* - * the qemud daemon program is only used within Android as a bridge - * between the emulator program and the emulated system. it really works as - * a simple stream multiplexer that works as follows: - * - * - qemud is started by init following instructions in - * /system/etc/init.goldfish.rc (i.e. it is never started on real devices) - * - * - qemud communicates with the emulator program through a single serial - * port, whose name is passed through a kernel boot parameter - * (e.g. android.qemud=ttyS1) - * - * - qemud binds one unix local stream socket (/dev/socket/qemud, created - * by init through /system/etc/init.goldfish.rc). - * - * - * emulator <==serial==> qemud <---> /dev/socket/qemud <-+--> client1 - * | - * +--> client2 - * - * - the protocol used on the serial connection is pretty simple: - * - * offset size description - * 0 2 2-char hex string giving the destination or - * source channel - * 2 4 4-char hex string giving the payload size - * 6 n the message payload - * - * for emulator->system messages, the 'channel' index indicates - * to which channel the payload must be sent - * - * for system->emulator messages, the 'channel' index indicates from - * which channel the payload comes from. - * - * the payload size is limited to MAX_SERIAL_PAYLOAD bytes. - * - * - communication between a client and qemud is stream based, but supports - * an optional framing mode which can be used to send packets larger than - * MAX_SERIAL_PAYLOAD bytes and provides packet bounding. - * - * offset size description - * 0 4 4-char hex string giving the payload size - * 6 n the message payload - * - * The size of the payload is then limited to 65535 bytes. - * - * - the special channel index 0 is used by the emulator and qemud only. - * other channel numbers correspond to clients. More specifically, - * connection are created like this: - * - * * the client connects to /dev/socket/qemud - * - * * the client sends the service name through the socket, as - * <service-name> - * - * * qemud creates a "Client" object internally, assigns it an - * internal unique channel number > 0, then sends a connection - * initiation request to the emulator (i.e. through channel 0): - * - * connect:<id>:<name> + * This implements support for the 'qemud' multiplexing communication + * channel between clients running in the emulated system and 'services' + * provided by the emulator. * - * where <name> is the service name, and <id> is a 2-hexchar - * number corresponding to the channel number. + * For additional details, please read docs/ANDROID-QEMUD.TXT * - * * in case of success, the emulator responds through channel 0 - * with: - * - * ok:connect:<id> - * - * after this, all messages between the client and the emulator - * are passed in pass-through mode. If the client closes the - * connection, qemud sends the following to the emulator: - * - * disconnect:<id> - * - * * if the emulator refuses the service connection, it will - * send the following through channel 0: - * - * ko:connect:<id>:reason-for-failure - * - * * any command sent through channel 0 to the emulator that is - * not properly recognized will be answered by: - * - * ko:unknown command - * - * Internally, the daemon maintains a "Client" object for each client - * connection (i.e. accepting socket connection). */ /* @@ -154,12 +80,18 @@ /** HANDLING INCOMING DATA FRAMES **/ +/* A QemudSink is just a handly data structure that is used to + * read a fixed amount of bytes into a buffer + */ typedef struct QemudSink { int len; int size; uint8_t* buff; } QemudSink; +/* reset a QemudSink, i.e. provide a new destination buffer address + * and its size in bytes. + */ static void qemud_sink_reset( QemudSink* ss, int size, uint8_t* buffer ) { @@ -168,6 +100,12 @@ qemud_sink_reset( QemudSink* ss, int size, uint8_t* buffer ) ss->buff = buffer; } +/* try to fill the sink by reading bytes from the source buffer + * '*pmsg' which contains '*plen' bytes + * + * this functions updates '*pmsg' and '*plen', and returns + * 1 if the sink's destination buffer is full, or 0 otherwise. + */ static int qemud_sink_fill( QemudSink* ss, const uint8_t* *pmsg, int *plen) { @@ -187,6 +125,9 @@ qemud_sink_fill( QemudSink* ss, const uint8_t* *pmsg, int *plen) return (ss->len == ss->size); } +/* returns the number of bytes needed to fill a sink's destination + * buffer. + */ static int qemud_sink_needed( QemudSink* ss ) { @@ -215,6 +156,17 @@ qemud_sink_needed( QemudSink* ss ) #define CHANNEL_OFFSET 0 #define CHANNEL_SIZE 2 +#if SUPPORT_LEGACY_QEMUD +typedef enum { + QEMUD_VERSION_UNKNOWN, + QEMUD_VERSION_LEGACY, + QEMUD_VERSION_NORMAL +} QemudVersion; + +# define LEGACY_LENGTH_OFFSET 0 +# define LEGACY_CHANNEL_OFFSET 4 +#endif + /* length of the framed header */ #define FRAME_HEADER_SIZE 4 @@ -233,6 +185,9 @@ typedef struct QemudSerial { int overflow; int in_size; int in_channel; +#if SUPPORT_LEGACY_QEMUD + QemudVersion version; +#endif QemudSink header[1]; QemudSink payload[1]; uint8_t data0[MAX_SERIAL_PAYLOAD+1]; @@ -293,9 +248,33 @@ qemud_serial_read( void* opaque, const uint8_t* from, int len ) if (!qemud_sink_fill(s->header, (const uint8_t**)&from, &len)) break; +#if SUPPORT_LEGACY_QEMUD + if (s->version == QEMUD_VERSION_UNKNOWN) { + /* if we receive "001200" as the first header, then we + * detected a legacy qemud daemon. See the comments + * in qemud_serial_send_legacy_probe() for details. + */ + if ( !memcmp(s->data0, "001200", 6) ) { + D("%s: legacy qemud detected.", __FUNCTION__); + s->version = QEMUD_VERSION_LEGACY; + } else { + D("%s: normal qemud detected.", __FUNCTION__); + s->version = QEMUD_VERSION_NORMAL; + } + } + + if (s->version == QEMUD_VERSION_LEGACY) { + s->in_size = hex2int( s->data0 + LEGACY_LENGTH_OFFSET, LENGTH_SIZE ); + s->in_channel = hex2int( s->data0 + LEGACY_CHANNEL_OFFSET, CHANNEL_SIZE ); + } else { + s->in_size = hex2int( s->data0 + LENGTH_OFFSET, LENGTH_SIZE ); + s->in_channel = hex2int( s->data0 + CHANNEL_OFFSET, CHANNEL_SIZE ); + } +#else /* extract payload length + channel id */ s->in_size = hex2int( s->data0 + LENGTH_OFFSET, LENGTH_SIZE ); s->in_channel = hex2int( s->data0 + CHANNEL_OFFSET, CHANNEL_SIZE ); +#endif s->header->len = 0; if (s->in_size <= 0 || s->in_channel < 0) { @@ -332,6 +311,68 @@ qemud_serial_read( void* opaque, const uint8_t* from, int len ) } } + +#if SUPPORT_LEGACY_QEMUD +static void +qemud_serial_send_legacy_probe( QemudSerial* s ) +{ + /* we're going to send a specially crafted packet to the qemud + * daemon, this will help us determine whether we're talking + * to a legacy or a normal daemon. + * + * the trick is to known that a legacy daemon uses the following + * header: + * + * <length><channel><payload> + * + * while the normal one uses: + * + * <channel><length><payload> + * + * where <channel> is a 2-hexchar string, and <length> a 4-hexchar + * string. + * + * if we send a header of "000100", it is interpreted: + * + * - as the header of a 1-byte payload by the legacy daemon + * - as the header of a 256-byte payload by the normal one. + * + * we're going to send something that looks like: + * + * "000100" + "X" + + * "000b00" + "connect:gsm" + + * "000b00" + "connect:gps" + + * "000f00" + "connect:control" + + * "00c210" + "0"*194 + * + * the normal daemon will interpret this as a 256-byte payload + * for channel 0, with garbage content ("X000b00conn...") which + * will be silently ignored. + * + * on the other hand, the legacy daemon will see it as a + * series of packets: + * + * one message "X" on channel 0, which will force the daemon + * to send back "001200ko:unknown command" as its first answer. + * + * three "connect:<xxx>" messages used to receive the channel + * numbers of the three legacy services implemented by the daemon. + * + * a garbage packet of 194 zeroes for channel 16, which will be + * silently ignored. + */ + uint8_t tab[194]; + + memset(tab, 0, sizeof(tab)); + qemu_chr_write(s->cs, (uint8_t*)"000100X", 7); + qemu_chr_write(s->cs, (uint8_t*)"000b00connect:gsm", 17); + qemu_chr_write(s->cs, (uint8_t*)"000b00connect:gps", 17); + qemu_chr_write(s->cs, (uint8_t*)"000f00connect:control", 21); + qemu_chr_write(s->cs, (uint8_t*)"00c210", 6); + qemu_chr_write(s->cs, tab, sizeof(tab)); +} +#endif /* SUPPORT_LEGACY_QEMUD */ + /* intialize a QemudSerial object with a charpipe endpoint * and a receiver. */ @@ -351,6 +392,11 @@ qemud_serial_init( QemudSerial* s, s->in_size = 0; s->in_channel = -1; +#if SUPPORT_LEGACY_QEMUD + s->version = QEMUD_VERSION_UNKNOWN; + qemud_serial_send_legacy_probe(s); +#endif + qemu_chr_add_handlers( cs, qemud_serial_can_read, qemud_serial_read, @@ -391,13 +437,25 @@ qemud_serial_send( QemudSerial* s, avail = MAX_SERIAL_PAYLOAD; /* write this packet's header */ +#if SUPPORT_LEGACY_QEMUD + if (s->version == QEMUD_VERSION_LEGACY) { + int2hex(header + LEGACY_LENGTH_OFFSET, LENGTH_SIZE, avail); + int2hex(header + LEGACY_CHANNEL_OFFSET, CHANNEL_SIZE, channel); + } else { + int2hex(header + LENGTH_OFFSET, LENGTH_SIZE, avail); + int2hex(header + CHANNEL_OFFSET, CHANNEL_SIZE, channel); + } +#else int2hex(header + LENGTH_OFFSET, LENGTH_SIZE, avail); int2hex(header + CHANNEL_OFFSET, CHANNEL_SIZE, channel); +#endif + T("%s: '%.*s'", __FUNCTION__, HEADER_SIZE, header); qemu_chr_write(s->cs, header, HEADER_SIZE); /* insert frame header when needed */ if (framing) { int2hex(frame, FRAME_HEADER_SIZE, msglen); + T("%s: '%.*s'", __FUNCTION__, FRAME_HEADER_SIZE, frame); qemu_chr_write(s->cs, frame, FRAME_HEADER_SIZE); avail -= FRAME_HEADER_SIZE; len -= FRAME_HEADER_SIZE; @@ -405,6 +463,7 @@ qemud_serial_send( QemudSerial* s, } /* write message content */ + T("%s: '%.*s'", __FUNCTION__, avail, msg); qemu_chr_write(s->cs, msg, avail); msg += avail; len -= avail; @@ -894,6 +953,61 @@ qemud_multiplexer_control_recv( void* opaque, return; } +#if SUPPORT_LEGACY_QEMUD + /* an ok:connect:<service>:<id> message can be received if we're + * talking to a legacy qemud daemon, i.e. one running in a 1.0 or + * 1.1 system image. + * + * we should treat is as a normal "connect:" attempt, except that + * we must not send back any acknowledgment. + */ + if (msglen > 11 && !memcmp(msg, "ok:connect:", 11)) { + const char* service_name = (const char*)msg + 11; + char* q = strchr(service_name, ':'); + int channel; + + if (q == NULL || q+3 != (char*)msgend) { + D("%s: malformed legacy connect message: '%.*s' (offset=%d)", + __FUNCTION__, msglen, (const char*)msg, q ? q-(char*)msg : -1); + return; + } + *q++ = 0; /* zero-terminate service name */ + channel = hex2int((uint8_t*)q, 2); + if (channel <= 0) { + D("%s: malformed legacy channel id '%.*s", + __FUNCTION__, 2, q); + return; + } + + switch (mult->serial->version) { + case QEMUD_VERSION_UNKNOWN: + mult->serial->version = QEMUD_VERSION_LEGACY; + D("%s: legacy qemud daemon detected.", __FUNCTION__); + break; + + case QEMUD_VERSION_LEGACY: + /* nothing unusual */ + break; + + default: + D("%s: weird, ignoring legacy qemud control message: '%.*s'", + __FUNCTION__, msglen, msg); + return; + } + + /* "hw-control" was called "control" in 1.0/1.1 */ + if (!strcmp(service_name,"control")) + service_name = "hw-control"; + + qemud_multiplexer_connect(mult, service_name, channel); + return; + } + + /* anything else, don't answer for legacy */ + if (mult->serial->version == QEMUD_VERSION_LEGACY) + return; +#endif /* SUPPORT_LEGACY_QEMUD */ + /* anything else is a problem */ p = bufprint(tmp, end, "ko:unknown command"); qemud_serial_send(mult->serial, 0, 0, (uint8_t*)tmp, p-tmp); diff --git a/android/main.c b/android/main.c index efa79aa..ed005d2 100644 --- a/android/main.c +++ b/android/main.c @@ -484,14 +484,40 @@ android_emulator_set_window_scale( double scale, int is_dpi ) static void qemulator_set_title( QEmulator* emulator ) { - char temp[64]; + char temp[128], *p=temp, *end=p+sizeof temp;; if (emulator->window == NULL) return; - snprintf( temp, sizeof(temp), "Android Emulator (%s:%d)", - avdInfo_getName( android_avdInfo ), - android_base_port ); + if (emulator->show_trackball) { + SkinKeyBinding bindings[ SKIN_KEY_COMMAND_MAX_BINDINGS ]; + int count; + + count = skin_keyset_get_bindings( android_keyset, + SKIN_KEY_COMMAND_TOGGLE_TRACKBALL, + bindings ); + + if (count > 0) { + int nn; + p = bufprint( p, end, "Press " ); + for (nn = 0; nn < count; nn++) { + if (nn > 0) { + if (nn < count-1) + p = bufprint(p, end, ", "); + else + p = bufprint(p, end, " or "); + } + p = bufprint(p, end, "%s", + skin_key_symmod_to_str( bindings[nn].sym, + bindings[nn].mod ) ); + } + p = bufprint(p, end, " to leave trackball mode. "); + } + } + + p = bufprint(p, end, "Android Emulator (%s:%d)", + avdInfo_getName( android_avdInfo ), + android_base_port ); skin_window_set_title( emulator->window, temp ); } @@ -778,6 +804,7 @@ handle_key_command( void* opaque, SkinKeyCommand command, int down ) case SKIN_KEY_COMMAND_TOGGLE_TRACKBALL: emulator->show_trackball = !emulator->show_trackball; skin_window_show_trackball( emulator->window, emulator->show_trackball ); + qemulator_set_title( emulator ); break; case SKIN_KEY_COMMAND_ONION_ALPHA_UP: diff --git a/docs/ANDROID-QEMUD-SERVICES.TXT b/docs/ANDROID-QEMUD-SERVICES.TXT new file mode 100644 index 0000000..24636e5 --- /dev/null +++ b/docs/ANDROID-QEMUD-SERVICES.TXT @@ -0,0 +1,155 @@ +ANDROID QEMUD SERVICES + +The docs/ANDROID-QEMUD.TXT document describes how clients running in the +emulated system can communicate with the emulator through the 'qemud' +multiplexing daemon. + +This document lists all services officially provided by the emulator to +clients. Each service is identified by a unique name. There is no provision +for versioning. Instead, if you need new features, create a new service +with a slightly different name and modify clients to use it in the system +image. + + +"gsm" service: +-------------- + + The GSM service provides a communication channel where GSM/GPRS AT commands + can be exchanged between the emulated system and an emulated modem. + + The messages consist in un-framed character messages as they appear in a + regular AT command channel (i.e. terminated by \r\n most of the time). + + There can be only 1 client talking to the modem, since the AT command + protocol does not allow for anything more complex. + + Implementation: telephony/modem_driver.c + Since: SDK 1.0 + +"gps" service: +-------------- + + The GPS service is used to broadcast NMEA 0183 sentences containing fix + information to all clients. The service doesn't listen to clients at all. + + All sentences are un-framed and end with a \n character. + + Implementation: android/gps.c + Since: SDK 1.0 + + +"hw-control" / "control" service: +--------------------- + + This service is named "control" in 1.0 and 1.1, and "hw-control" + in 1.5 and later releases of the SDK. + + This service is used to allow the emulated system to control various aspects + of hardware emulation. The corresponding clients are in + hardware/libhardware_legacy in the Android source tree. + + All messages use the optional framing described in ANDROID-QEMUD.TXT. + Only one client can talk with the service at any one time, but clients + quickly connect/disconnect to the service anyway. + + Supported messages are: + + 1/ Client sends "power:light:brightness:<lightname>:<val>", where + <lightname> is the name of a light and <val> is an decimal value + between 0 (off) and 255 (brightest). Valid values for 'light' are: + + lcd_backlight + keyboard_backlight + button_backlight + + Currently, only 'lcd_backlight' is supported by the emulator. + Note that the brightness value doesn't scale linearly on physical + devices. + + 2/ Client sends "set_led_state:<color-rgb>:<on-ms>:<off-ms>" where + <color-rgb> is an 8-hexchar value describing an xRGB color, and + <on-ms> and <off-ms> are decimal values expressing timeout in + milliseconds. + + This is used to modify the color of the notification LED on the + emulated phone as well as provide on/off timeouts for flashing. + + cCrrently unsupported by the emulator. + + 3/ Client sends "vibrator:<timeout>", where <timeout> is a decimal value. + used to enable vibrator emulation for <timeout> milli-seconds. + + Currently unsupported by the emulator. + + + Implementation: android/hw-control.c + Since: SDK 1.0 + + +"sensors" service: +------------------ + + This service is used for sensor emulation. All messages are framed and the + protocol is the following: + + 1/ Clients initially sends "list-sensors" and receives a single string + containing a decimal mask value. The value is a set of bit-flags + indicating which sensors are provided by the hardware emulation. The + bit flags are defined as: + + bit 0: accelerometer + bit 1: compass + bit 2: orientation + bit 3: temperature + + 2/ Client sends "set-delay:<delay-ms>", where <delay-ms> is a decimal + string, to set the minimum delay in milliseconds between sensor event + reports it wants to receive. + + 3/ Client sends "wake", the service must immediately send back "wake" as + an answer. This is used to simplify parts of the client emulation. + + 4/ Client sends "set:<sensor-name>:<flag>", where <sensor-name> is the + name of one of the emulated sensors, and <flag> is either "0" or "1". + This is used to enable or disable the report of data from a given + emulated sensor hardware. + + the list of valid <sensor-name> values is the following: + + acceleration : for the accelerometer + magnetic-field : for the compass + orientation : for the orientation sensor + temperature : for the temperature sensor + + + 5/ If at least one of the emulated sensors has been enabled with + "set:<name>:1", then the service should broadcast periodically the + state of sensors. + + this is done by broadcasting a series of framed messages that look + like: + + acceleration:<x>:<y>:<z> + magnetic-field:<x>:<y>:<z> + orientation:<azimuth>:<pitch>:<roll> + temperature:<celsius> + sync:<time_us> + + Note that each line, corresponds to an independent framed message. + Each line, except the last one, is optional and should only be + produced if the corresponding sensor reporting has been enabled + through "set:<name>:1". + + <x>, <y>, <z>, <azimuth>, <pitch>, <roll> and <celsius> are floating + point values written as strings with the "%g" printf formatting option. + + The last 'sync' message is required to end the broadcast and contains + the current VM clock time in micro-seconds. The client will adjust it + internally to only care about differences between sensor broadcasts. + + If reporting is disabled for all sensors, no broadcast message needs + to be sent back to clients. + + + Implementation: android/hw-sensors.c + Since: SDK 1.5 (cupcake) diff --git a/docs/ANDROID-QEMUD.TXT b/docs/ANDROID-QEMUD.TXT index 6415a47..1364553 100644 --- a/docs/ANDROID-QEMUD.TXT +++ b/docs/ANDROID-QEMUD.TXT @@ -92,7 +92,7 @@ Since the "cupcake" platform, this works as follows: connect:<service>:<id> - where <service> is the service name, and <id> is a 4-hexchar string + where <service> is the service name, and <id> is a 2-hexchar string giving the allocated channel index for the client. @@ -181,9 +181,19 @@ This is documented here since this explains some subtleties in the implementation code of android/hw-qemud.c The old scheme also used a serial port to allow the daemon and the emulator -to communicate. Besides, the same multiplexing protocol was used to pass -messages through multiple channels. However, several other differences -exist, best illustrated by the following graphics: +to communicate. However, the multiplexing protocol swaps the position of +'channel' and 'length' in the header: + + offset size description + + 0 4 4-char hex string giving the payload size + + 4 2 2-char hex string giving the destination or + source channel + + 6 n the message payload + +Several other differences, best illustrated by the following graphics: emulator <==serial==> qemud <-+--> /dev/socket/qemud_gsm <--> GSM client | |