aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--emulator/gps/Android.mk39
-rw-r--r--emulator/gps/gps_qemu.c941
-rw-r--r--emulator/qemud/Android.mk25
-rw-r--r--emulator/qemud/qemud.c1719
-rw-r--r--emulator/sensors/Android.mk38
-rw-r--r--emulator/sensors/sensors_qemu.c637
-rw-r--r--emulator/tests/Android.mk17
-rw-r--r--emulator/tests/test-qemud-pipes.c113
-rw-r--r--emulator/tools/Android.mk44
-rw-r--r--emulator/tools/qemu-props.c116
-rw-r--r--layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java13
-rwxr-xr-xmonkeyrunner/etc/monkeyrunner25
-rw-r--r--monkeyrunner/etc/monkeyrunner.bat19
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java109
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java194
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java16
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java5
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java24
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java12
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java4
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java12
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java8
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java10
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyBackend.java (renamed from monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java)15
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyDevice.java200
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyImage.java36
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/core/MonkeyImageBase.java219
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/easy/By.java5
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java91
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java34
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java7
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java16
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java4
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java4
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java3
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java3
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java7
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java7
-rw-r--r--monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java14
-rw-r--r--ninepatch/tests/Android.mk1
-rw-r--r--ninepatch/tests/res/com/android/ninepatch/button.9.png (renamed from ninepatch/tests/src/com/android/ninepatch/button.9.png)bin3750 -> 3750 bytes
41 files changed, 4452 insertions, 354 deletions
diff --git a/emulator/gps/Android.mk b/emulator/gps/Android.mk
new file mode 100644
index 0000000..41bdc64
--- /dev/null
+++ b/emulator/gps/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2010 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.
+
+
+# We're moving the emulator-specific platform libs to
+# development.git/tools/emulator/. The following test is to ensure
+# smooth builds even if the tree contains both versions.
+#
+ifndef BUILD_EMULATOR_GPS_MODULE
+BUILD_EMULATOR_GPS_MODULE := true
+
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TARGET_PRODUCT),sim)
+# HAL module implemenation, not prelinked and stored in
+# hw/<GPS_HARDWARE_MODULE_ID>.<ro.hardware>.so
+include $(CLEAR_VARS)
+LOCAL_PRELINK_MODULE := false
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_CFLAGS += -DQEMU_HARDWARE
+LOCAL_SHARED_LIBRARIES := liblog libcutils libhardware
+LOCAL_SRC_FILES := gps_qemu.c
+LOCAL_MODULE := gps.goldfish
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_SHARED_LIBRARY)
+endif
+
+endif # BUILD_EMULATOR_GPS_MODULE
diff --git a/emulator/gps/gps_qemu.c b/emulator/gps/gps_qemu.c
new file mode 100644
index 0000000..a4699d3
--- /dev/null
+++ b/emulator/gps/gps_qemu.c
@@ -0,0 +1,941 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+/* this implements a GPS hardware library for the Android emulator.
+ * the following code should be built as a shared library that will be
+ * placed into /system/lib/hw/gps.goldfish.so
+ *
+ * it will be loaded by the code in hardware/libhardware/hardware.c
+ * which is itself called from android_location_GpsLocationProvider.cpp
+ */
+
+
+#include <errno.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <math.h>
+#include <time.h>
+
+#define LOG_TAG "gps_qemu"
+#include <cutils/log.h>
+#include <cutils/sockets.h>
+#include <hardware/gps.h>
+#include <hardware/qemud.h>
+
+/* the name of the qemud-controlled socket */
+#define QEMU_CHANNEL_NAME "gps"
+
+#define GPS_DEBUG 0
+
+#if GPS_DEBUG
+# define D(...) LOGD(__VA_ARGS__)
+#else
+# define D(...) ((void)0)
+#endif
+
+/*****************************************************************/
+/*****************************************************************/
+/***** *****/
+/***** N M E A T O K E N I Z E R *****/
+/***** *****/
+/*****************************************************************/
+/*****************************************************************/
+
+typedef struct {
+ const char* p;
+ const char* end;
+} Token;
+
+#define MAX_NMEA_TOKENS 16
+
+typedef struct {
+ int count;
+ Token tokens[ MAX_NMEA_TOKENS ];
+} NmeaTokenizer;
+
+static int
+nmea_tokenizer_init( NmeaTokenizer* t, const char* p, const char* end )
+{
+ int count = 0;
+ char* q;
+
+ // the initial '$' is optional
+ if (p < end && p[0] == '$')
+ p += 1;
+
+ // remove trailing newline
+ if (end > p && end[-1] == '\n') {
+ end -= 1;
+ if (end > p && end[-1] == '\r')
+ end -= 1;
+ }
+
+ // get rid of checksum at the end of the sentecne
+ if (end >= p+3 && end[-3] == '*') {
+ end -= 3;
+ }
+
+ while (p < end) {
+ const char* q = p;
+
+ q = memchr(p, ',', end-p);
+ if (q == NULL)
+ q = end;
+
+ if (q > p) {
+ if (count < MAX_NMEA_TOKENS) {
+ t->tokens[count].p = p;
+ t->tokens[count].end = q;
+ count += 1;
+ }
+ }
+ if (q < end)
+ q += 1;
+
+ p = q;
+ }
+
+ t->count = count;
+ return count;
+}
+
+static Token
+nmea_tokenizer_get( NmeaTokenizer* t, int index )
+{
+ Token tok;
+ static const char* dummy = "";
+
+ if (index < 0 || index >= t->count) {
+ tok.p = tok.end = dummy;
+ } else
+ tok = t->tokens[index];
+
+ return tok;
+}
+
+
+static int
+str2int( const char* p, const char* end )
+{
+ int result = 0;
+ int len = end - p;
+
+ for ( ; len > 0; len--, p++ )
+ {
+ int c;
+
+ if (p >= end)
+ goto Fail;
+
+ c = *p - '0';
+ if ((unsigned)c >= 10)
+ goto Fail;
+
+ result = result*10 + c;
+ }
+ return result;
+
+Fail:
+ return -1;
+}
+
+static double
+str2float( const char* p, const char* end )
+{
+ int result = 0;
+ int len = end - p;
+ char temp[16];
+
+ if (len >= (int)sizeof(temp))
+ return 0.;
+
+ memcpy( temp, p, len );
+ temp[len] = 0;
+ return strtod( temp, NULL );
+}
+
+/*****************************************************************/
+/*****************************************************************/
+/***** *****/
+/***** N M E A P A R S E R *****/
+/***** *****/
+/*****************************************************************/
+/*****************************************************************/
+
+#define NMEA_MAX_SIZE 83
+
+typedef struct {
+ int pos;
+ int overflow;
+ int utc_year;
+ int utc_mon;
+ int utc_day;
+ int utc_diff;
+ GpsLocation fix;
+ gps_location_callback callback;
+ char in[ NMEA_MAX_SIZE+1 ];
+} NmeaReader;
+
+
+static void
+nmea_reader_update_utc_diff( NmeaReader* r )
+{
+ time_t now = time(NULL);
+ struct tm tm_local;
+ struct tm tm_utc;
+ long time_local, time_utc;
+
+ gmtime_r( &now, &tm_utc );
+ localtime_r( &now, &tm_local );
+
+ time_local = tm_local.tm_sec +
+ 60*(tm_local.tm_min +
+ 60*(tm_local.tm_hour +
+ 24*(tm_local.tm_yday +
+ 365*tm_local.tm_year)));
+
+ time_utc = tm_utc.tm_sec +
+ 60*(tm_utc.tm_min +
+ 60*(tm_utc.tm_hour +
+ 24*(tm_utc.tm_yday +
+ 365*tm_utc.tm_year)));
+
+ r->utc_diff = time_utc - time_local;
+}
+
+
+static void
+nmea_reader_init( NmeaReader* r )
+{
+ memset( r, 0, sizeof(*r) );
+
+ r->pos = 0;
+ r->overflow = 0;
+ r->utc_year = -1;
+ r->utc_mon = -1;
+ r->utc_day = -1;
+ r->callback = NULL;
+ r->fix.size = sizeof(r->fix);
+
+ nmea_reader_update_utc_diff( r );
+}
+
+
+static void
+nmea_reader_set_callback( NmeaReader* r, gps_location_callback cb )
+{
+ r->callback = cb;
+ if (cb != NULL && r->fix.flags != 0) {
+ D("%s: sending latest fix to new callback", __FUNCTION__);
+ r->callback( &r->fix );
+ r->fix.flags = 0;
+ }
+}
+
+
+static int
+nmea_reader_update_time( NmeaReader* r, Token tok )
+{
+ int hour, minute;
+ double seconds;
+ struct tm tm;
+ time_t fix_time;
+
+ if (tok.p + 6 > tok.end)
+ return -1;
+
+ if (r->utc_year < 0) {
+ // no date yet, get current one
+ time_t now = time(NULL);
+ gmtime_r( &now, &tm );
+ r->utc_year = tm.tm_year + 1900;
+ r->utc_mon = tm.tm_mon + 1;
+ r->utc_day = tm.tm_mday;
+ }
+
+ hour = str2int(tok.p, tok.p+2);
+ minute = str2int(tok.p+2, tok.p+4);
+ seconds = str2float(tok.p+4, tok.end);
+
+ tm.tm_hour = hour;
+ tm.tm_min = minute;
+ tm.tm_sec = (int) seconds;
+ tm.tm_year = r->utc_year - 1900;
+ tm.tm_mon = r->utc_mon - 1;
+ tm.tm_mday = r->utc_day;
+ tm.tm_isdst = -1;
+
+ fix_time = mktime( &tm ) + r->utc_diff;
+ r->fix.timestamp = (long long)fix_time * 1000;
+ return 0;
+}
+
+static int
+nmea_reader_update_date( NmeaReader* r, Token date, Token time )
+{
+ Token tok = date;
+ int day, mon, year;
+
+ if (tok.p + 6 != tok.end) {
+ D("date not properly formatted: '%.*s'", tok.end-tok.p, tok.p);
+ return -1;
+ }
+ day = str2int(tok.p, tok.p+2);
+ mon = str2int(tok.p+2, tok.p+4);
+ year = str2int(tok.p+4, tok.p+6) + 2000;
+
+ if ((day|mon|year) < 0) {
+ D("date not properly formatted: '%.*s'", tok.end-tok.p, tok.p);
+ return -1;
+ }
+
+ r->utc_year = year;
+ r->utc_mon = mon;
+ r->utc_day = day;
+
+ return nmea_reader_update_time( r, time );
+}
+
+
+static double
+convert_from_hhmm( Token tok )
+{
+ double val = str2float(tok.p, tok.end);
+ int degrees = (int)(floor(val) / 100);
+ double minutes = val - degrees*100.;
+ double dcoord = degrees + minutes / 60.0;
+ return dcoord;
+}
+
+
+static int
+nmea_reader_update_latlong( NmeaReader* r,
+ Token latitude,
+ char latitudeHemi,
+ Token longitude,
+ char longitudeHemi )
+{
+ double lat, lon;
+ Token tok;
+
+ tok = latitude;
+ if (tok.p + 6 > tok.end) {
+ D("latitude is too short: '%.*s'", tok.end-tok.p, tok.p);
+ return -1;
+ }
+ lat = convert_from_hhmm(tok);
+ if (latitudeHemi == 'S')
+ lat = -lat;
+
+ tok = longitude;
+ if (tok.p + 6 > tok.end) {
+ D("longitude is too short: '%.*s'", tok.end-tok.p, tok.p);
+ return -1;
+ }
+ lon = convert_from_hhmm(tok);
+ if (longitudeHemi == 'W')
+ lon = -lon;
+
+ r->fix.flags |= GPS_LOCATION_HAS_LAT_LONG;
+ r->fix.latitude = lat;
+ r->fix.longitude = lon;
+ return 0;
+}
+
+
+static int
+nmea_reader_update_altitude( NmeaReader* r,
+ Token altitude,
+ Token units )
+{
+ double alt;
+ Token tok = altitude;
+
+ if (tok.p >= tok.end)
+ return -1;
+
+ r->fix.flags |= GPS_LOCATION_HAS_ALTITUDE;
+ r->fix.altitude = str2float(tok.p, tok.end);
+ return 0;
+}
+
+
+static int
+nmea_reader_update_bearing( NmeaReader* r,
+ Token bearing )
+{
+ double alt;
+ Token tok = bearing;
+
+ if (tok.p >= tok.end)
+ return -1;
+
+ r->fix.flags |= GPS_LOCATION_HAS_BEARING;
+ r->fix.bearing = str2float(tok.p, tok.end);
+ return 0;
+}
+
+
+static int
+nmea_reader_update_speed( NmeaReader* r,
+ Token speed )
+{
+ double alt;
+ Token tok = speed;
+
+ if (tok.p >= tok.end)
+ return -1;
+
+ r->fix.flags |= GPS_LOCATION_HAS_SPEED;
+ r->fix.speed = str2float(tok.p, tok.end);
+ return 0;
+}
+
+
+static void
+nmea_reader_parse( NmeaReader* r )
+{
+ /* we received a complete sentence, now parse it to generate
+ * a new GPS fix...
+ */
+ NmeaTokenizer tzer[1];
+ Token tok;
+
+ D("Received: '%.*s'", r->pos, r->in);
+ if (r->pos < 9) {
+ D("Too short. discarded.");
+ return;
+ }
+
+ nmea_tokenizer_init(tzer, r->in, r->in + r->pos);
+#if GPS_DEBUG
+ {
+ int n;
+ D("Found %d tokens", tzer->count);
+ for (n = 0; n < tzer->count; n++) {
+ Token tok = nmea_tokenizer_get(tzer,n);
+ D("%2d: '%.*s'", n, tok.end-tok.p, tok.p);
+ }
+ }
+#endif
+
+ tok = nmea_tokenizer_get(tzer, 0);
+ if (tok.p + 5 > tok.end) {
+ D("sentence id '%.*s' too short, ignored.", tok.end-tok.p, tok.p);
+ return;
+ }
+
+ // ignore first two characters.
+ tok.p += 2;
+ if ( !memcmp(tok.p, "GGA", 3) ) {
+ // GPS fix
+ Token tok_time = nmea_tokenizer_get(tzer,1);
+ Token tok_latitude = nmea_tokenizer_get(tzer,2);
+ Token tok_latitudeHemi = nmea_tokenizer_get(tzer,3);
+ Token tok_longitude = nmea_tokenizer_get(tzer,4);
+ Token tok_longitudeHemi = nmea_tokenizer_get(tzer,5);
+ Token tok_altitude = nmea_tokenizer_get(tzer,9);
+ Token tok_altitudeUnits = nmea_tokenizer_get(tzer,10);
+
+ nmea_reader_update_time(r, tok_time);
+ nmea_reader_update_latlong(r, tok_latitude,
+ tok_latitudeHemi.p[0],
+ tok_longitude,
+ tok_longitudeHemi.p[0]);
+ nmea_reader_update_altitude(r, tok_altitude, tok_altitudeUnits);
+
+ } else if ( !memcmp(tok.p, "GSA", 3) ) {
+ // do something ?
+ } else if ( !memcmp(tok.p, "RMC", 3) ) {
+ Token tok_time = nmea_tokenizer_get(tzer,1);
+ Token tok_fixStatus = nmea_tokenizer_get(tzer,2);
+ Token tok_latitude = nmea_tokenizer_get(tzer,3);
+ Token tok_latitudeHemi = nmea_tokenizer_get(tzer,4);
+ Token tok_longitude = nmea_tokenizer_get(tzer,5);
+ Token tok_longitudeHemi = nmea_tokenizer_get(tzer,6);
+ Token tok_speed = nmea_tokenizer_get(tzer,7);
+ Token tok_bearing = nmea_tokenizer_get(tzer,8);
+ Token tok_date = nmea_tokenizer_get(tzer,9);
+
+ D("in RMC, fixStatus=%c", tok_fixStatus.p[0]);
+ if (tok_fixStatus.p[0] == 'A')
+ {
+ nmea_reader_update_date( r, tok_date, tok_time );
+
+ nmea_reader_update_latlong( r, tok_latitude,
+ tok_latitudeHemi.p[0],
+ tok_longitude,
+ tok_longitudeHemi.p[0] );
+
+ nmea_reader_update_bearing( r, tok_bearing );
+ nmea_reader_update_speed ( r, tok_speed );
+ }
+ } else {
+ tok.p -= 2;
+ D("unknown sentence '%.*s", tok.end-tok.p, tok.p);
+ }
+ if (r->fix.flags != 0) {
+#if GPS_DEBUG
+ char temp[256];
+ char* p = temp;
+ char* end = p + sizeof(temp);
+ struct tm utc;
+
+ p += snprintf( p, end-p, "sending fix" );
+ if (r->fix.flags & GPS_LOCATION_HAS_LAT_LONG) {
+ p += snprintf(p, end-p, " lat=%g lon=%g", r->fix.latitude, r->fix.longitude);
+ }
+ if (r->fix.flags & GPS_LOCATION_HAS_ALTITUDE) {
+ p += snprintf(p, end-p, " altitude=%g", r->fix.altitude);
+ }
+ if (r->fix.flags & GPS_LOCATION_HAS_SPEED) {
+ p += snprintf(p, end-p, " speed=%g", r->fix.speed);
+ }
+ if (r->fix.flags & GPS_LOCATION_HAS_BEARING) {
+ p += snprintf(p, end-p, " bearing=%g", r->fix.bearing);
+ }
+ if (r->fix.flags & GPS_LOCATION_HAS_ACCURACY) {
+ p += snprintf(p,end-p, " accuracy=%g", r->fix.accuracy);
+ }
+ gmtime_r( (time_t*) &r->fix.timestamp, &utc );
+ p += snprintf(p, end-p, " time=%s", asctime( &utc ) );
+ D(temp);
+#endif
+ if (r->callback) {
+ r->callback( &r->fix );
+ r->fix.flags = 0;
+ }
+ else {
+ D("no callback, keeping data until needed !");
+ }
+ }
+}
+
+
+static void
+nmea_reader_addc( NmeaReader* r, int c )
+{
+ if (r->overflow) {
+ r->overflow = (c != '\n');
+ return;
+ }
+
+ if (r->pos >= (int) sizeof(r->in)-1 ) {
+ r->overflow = 1;
+ r->pos = 0;
+ return;
+ }
+
+ r->in[r->pos] = (char)c;
+ r->pos += 1;
+
+ if (c == '\n') {
+ nmea_reader_parse( r );
+ r->pos = 0;
+ }
+}
+
+
+/*****************************************************************/
+/*****************************************************************/
+/***** *****/
+/***** C O N N E C T I O N S T A T E *****/
+/***** *****/
+/*****************************************************************/
+/*****************************************************************/
+
+/* commands sent to the gps thread */
+enum {
+ CMD_QUIT = 0,
+ CMD_START = 1,
+ CMD_STOP = 2
+};
+
+
+/* this is the state of our connection to the qemu_gpsd daemon */
+typedef struct {
+ int init;
+ int fd;
+ GpsCallbacks callbacks;
+ pthread_t thread;
+ int control[2];
+} GpsState;
+
+static GpsState _gps_state[1];
+
+
+static void
+gps_state_done( GpsState* s )
+{
+ // tell the thread to quit, and wait for it
+ char cmd = CMD_QUIT;
+ void* dummy;
+ write( s->control[0], &cmd, 1 );
+ pthread_join(s->thread, &dummy);
+
+ // close the control socket pair
+ close( s->control[0] ); s->control[0] = -1;
+ close( s->control[1] ); s->control[1] = -1;
+
+ // close connection to the QEMU GPS daemon
+ close( s->fd ); s->fd = -1;
+ s->init = 0;
+}
+
+
+static void
+gps_state_start( GpsState* s )
+{
+ char cmd = CMD_START;
+ int ret;
+
+ do { ret=write( s->control[0], &cmd, 1 ); }
+ while (ret < 0 && errno == EINTR);
+
+ if (ret != 1)
+ D("%s: could not send CMD_START command: ret=%d: %s",
+ __FUNCTION__, ret, strerror(errno));
+}
+
+
+static void
+gps_state_stop( GpsState* s )
+{
+ char cmd = CMD_STOP;
+ int ret;
+
+ do { ret=write( s->control[0], &cmd, 1 ); }
+ while (ret < 0 && errno == EINTR);
+
+ if (ret != 1)
+ D("%s: could not send CMD_STOP command: ret=%d: %s",
+ __FUNCTION__, ret, strerror(errno));
+}
+
+
+static int
+epoll_register( int epoll_fd, int fd )
+{
+ struct epoll_event ev;
+ int ret, flags;
+
+ /* important: make the fd non-blocking */
+ flags = fcntl(fd, F_GETFL);
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+
+ ev.events = EPOLLIN;
+ ev.data.fd = fd;
+ do {
+ ret = epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &ev );
+ } while (ret < 0 && errno == EINTR);
+ return ret;
+}
+
+
+static int
+epoll_deregister( int epoll_fd, int fd )
+{
+ int ret;
+ do {
+ ret = epoll_ctl( epoll_fd, EPOLL_CTL_DEL, fd, NULL );
+ } while (ret < 0 && errno == EINTR);
+ return ret;
+}
+
+/* this is the main thread, it waits for commands from gps_state_start/stop and,
+ * when started, messages from the QEMU GPS daemon. these are simple NMEA sentences
+ * that must be parsed to be converted into GPS fixes sent to the framework
+ */
+static void
+gps_state_thread( void* arg )
+{
+ GpsState* state = (GpsState*) arg;
+ NmeaReader reader[1];
+ int epoll_fd = epoll_create(2);
+ int started = 0;
+ int gps_fd = state->fd;
+ int control_fd = state->control[1];
+
+ nmea_reader_init( reader );
+
+ // register control file descriptors for polling
+ epoll_register( epoll_fd, control_fd );
+ epoll_register( epoll_fd, gps_fd );
+
+ D("gps thread running");
+
+ // now loop
+ for (;;) {
+ struct epoll_event events[2];
+ int ne, nevents;
+
+ nevents = epoll_wait( epoll_fd, events, 2, -1 );
+ if (nevents < 0) {
+ if (errno != EINTR)
+ LOGE("epoll_wait() unexpected error: %s", strerror(errno));
+ continue;
+ }
+ D("gps thread received %d events", nevents);
+ for (ne = 0; ne < nevents; ne++) {
+ if ((events[ne].events & (EPOLLERR|EPOLLHUP)) != 0) {
+ LOGE("EPOLLERR or EPOLLHUP after epoll_wait() !?");
+ return;
+ }
+ if ((events[ne].events & EPOLLIN) != 0) {
+ int fd = events[ne].data.fd;
+
+ if (fd == control_fd)
+ {
+ char cmd = 255;
+ int ret;
+ D("gps control fd event");
+ do {
+ ret = read( fd, &cmd, 1 );
+ } while (ret < 0 && errno == EINTR);
+
+ if (cmd == CMD_QUIT) {
+ D("gps thread quitting on demand");
+ return;
+ }
+ else if (cmd == CMD_START) {
+ if (!started) {
+ D("gps thread starting location_cb=%p", state->callbacks.location_cb);
+ started = 1;
+ nmea_reader_set_callback( reader, state->callbacks.location_cb );
+ }
+ }
+ else if (cmd == CMD_STOP) {
+ if (started) {
+ D("gps thread stopping");
+ started = 0;
+ nmea_reader_set_callback( reader, NULL );
+ }
+ }
+ }
+ else if (fd == gps_fd)
+ {
+ char buff[32];
+ D("gps fd event");
+ for (;;) {
+ int nn, ret;
+
+ ret = read( fd, buff, sizeof(buff) );
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EWOULDBLOCK)
+ LOGE("error while reading from gps daemon socket: %s:", strerror(errno));
+ break;
+ }
+ D("received %d bytes: %.*s", ret, ret, buff);
+ for (nn = 0; nn < ret; nn++)
+ nmea_reader_addc( reader, buff[nn] );
+ }
+ D("gps fd event end");
+ }
+ else
+ {
+ LOGE("epoll_wait() returned unkown fd %d ?", fd);
+ }
+ }
+ }
+ }
+}
+
+
+static void
+gps_state_init( GpsState* state, GpsCallbacks* callbacks )
+{
+ state->init = 1;
+ state->control[0] = -1;
+ state->control[1] = -1;
+ state->fd = -1;
+
+ state->fd = qemud_channel_open(QEMU_CHANNEL_NAME);
+
+ if (state->fd < 0) {
+ D("no gps emulation detected");
+ return;
+ }
+
+ D("gps emulation will read from '%s' qemud channel", QEMU_CHANNEL_NAME );
+
+ if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, state->control ) < 0 ) {
+ LOGE("could not create thread control socket pair: %s", strerror(errno));
+ goto Fail;
+ }
+
+ state->thread = callbacks->create_thread_cb( "gps_state_thread", gps_state_thread, state );
+
+ if ( !state->thread ) {
+ LOGE("could not create gps thread: %s", strerror(errno));
+ goto Fail;
+ }
+
+ state->callbacks = *callbacks;
+
+ D("gps state initialized");
+ return;
+
+Fail:
+ gps_state_done( state );
+}
+
+
+/*****************************************************************/
+/*****************************************************************/
+/***** *****/
+/***** I N T E R F A C E *****/
+/***** *****/
+/*****************************************************************/
+/*****************************************************************/
+
+
+static int
+qemu_gps_init(GpsCallbacks* callbacks)
+{
+ GpsState* s = _gps_state;
+
+ if (!s->init)
+ gps_state_init(s, callbacks);
+
+ if (s->fd < 0)
+ return -1;
+
+ return 0;
+}
+
+static void
+qemu_gps_cleanup(void)
+{
+ GpsState* s = _gps_state;
+
+ if (s->init)
+ gps_state_done(s);
+}
+
+
+static int
+qemu_gps_start()
+{
+ GpsState* s = _gps_state;
+
+ if (!s->init) {
+ D("%s: called with uninitialized state !!", __FUNCTION__);
+ return -1;
+ }
+
+ D("%s: called", __FUNCTION__);
+ gps_state_start(s);
+ return 0;
+}
+
+
+static int
+qemu_gps_stop()
+{
+ GpsState* s = _gps_state;
+
+ if (!s->init) {
+ D("%s: called with uninitialized state !!", __FUNCTION__);
+ return -1;
+ }
+
+ D("%s: called", __FUNCTION__);
+ gps_state_stop(s);
+ return 0;
+}
+
+
+static int
+qemu_gps_inject_time(GpsUtcTime time, int64_t timeReference, int uncertainty)
+{
+ return 0;
+}
+
+static int
+qemu_gps_inject_location(double latitude, double longitude, float accuracy)
+{
+ return 0;
+}
+
+static void
+qemu_gps_delete_aiding_data(GpsAidingData flags)
+{
+}
+
+static int qemu_gps_set_position_mode(GpsPositionMode mode, int fix_frequency)
+{
+ // FIXME - support fix_frequency
+ return 0;
+}
+
+static const void*
+qemu_gps_get_extension(const char* name)
+{
+ // no extensions supported
+ return NULL;
+}
+
+static const GpsInterface qemuGpsInterface = {
+ sizeof(GpsInterface),
+ qemu_gps_init,
+ qemu_gps_start,
+ qemu_gps_stop,
+ qemu_gps_cleanup,
+ qemu_gps_inject_time,
+ qemu_gps_inject_location,
+ qemu_gps_delete_aiding_data,
+ qemu_gps_set_position_mode,
+ qemu_gps_get_extension,
+};
+
+const GpsInterface* gps__get_gps_interface(struct gps_device_t* dev)
+{
+ return &qemuGpsInterface;
+}
+
+static int open_gps(const struct hw_module_t* module, char const* name,
+ struct hw_device_t** device)
+{
+ struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));
+ memset(dev, 0, sizeof(*dev));
+
+ dev->common.tag = HARDWARE_DEVICE_TAG;
+ dev->common.version = 0;
+ dev->common.module = (struct hw_module_t*)module;
+// dev->common.close = (int (*)(struct hw_device_t*))close_lights;
+ dev->get_gps_interface = gps__get_gps_interface;
+
+ *device = (struct hw_device_t*)dev;
+ return 0;
+}
+
+
+static struct hw_module_methods_t gps_module_methods = {
+ .open = open_gps
+};
+
+const struct hw_module_t HAL_MODULE_INFO_SYM = {
+ .tag = HARDWARE_MODULE_TAG,
+ .version_major = 1,
+ .version_minor = 0,
+ .id = GPS_HARDWARE_MODULE_ID,
+ .name = "Goldfish GPS Module",
+ .author = "The Android Open Source Project",
+ .methods = &gps_module_methods,
+};
diff --git a/emulator/qemud/Android.mk b/emulator/qemud/Android.mk
new file mode 100644
index 0000000..5666a74
--- /dev/null
+++ b/emulator/qemud/Android.mk
@@ -0,0 +1,25 @@
+# Copyright 2008 The Android Open Source Project
+
+# We're moving the emulator-specific platform libs to
+# development.git/tools/emulator/. The following test is to ensure
+# smooth builds even if the tree contains both versions.
+#
+ifndef BUILD_EMULATOR_QEMUD
+BUILD_EMULATOR_QEMUD := true
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ qemud.c
+
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+
+LOCAL_MODULE:= qemud
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
+
+endif # BUILD_EMULATOR_QEMUD \ No newline at end of file
diff --git a/emulator/qemud/qemud.c b/emulator/qemud/qemud.c
new file mode 100644
index 0000000..e1c7b54
--- /dev/null
+++ b/emulator/qemud/qemud.c
@@ -0,0 +1,1719 @@
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <termios.h>
+#include <cutils/sockets.h>
+
+/*
+ * 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 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>
+ *
+ * where <name> is the service name, and <id> is a 2-hexchar
+ * number corresponding to the channel number.
+ *
+ * * 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 emulator refuses the service connection, it will
+ * send the following through channel 0:
+ *
+ * ko:connect:<id>:reason-for-failure
+ *
+ * * If the client closes the connection, qemud sends the following
+ * to the emulator:
+ *
+ * disconnect:<id>
+ *
+ * The same message is the opposite direction if the emulator
+ * chooses to close the connection.
+ *
+ * * 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).
+ */
+
+/* name of the single control socket used by the daemon */
+#define CONTROL_SOCKET_NAME "qemud"
+
+#define DEBUG 1
+#define T_ACTIVE 0 /* set to 1 to dump traffic */
+
+#if DEBUG
+# define LOG_TAG "qemud"
+# include <cutils/log.h>
+# define D(...) LOGD(__VA_ARGS__)
+#else
+# define D(...) ((void)0)
+# define T(...) ((void)0)
+#endif
+
+#if T_ACTIVE
+# define T(...) D(__VA_ARGS__)
+#else
+# define T(...) ((void)0)
+#endif
+
+/** UTILITIES
+ **/
+
+static void
+fatal( const char* fmt, ... )
+{
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "PANIC: ");
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n" );
+ va_end(args);
+ exit(1);
+}
+
+static void*
+xalloc( size_t sz )
+{
+ void* p;
+
+ if (sz == 0)
+ return NULL;
+
+ p = malloc(sz);
+ if (p == NULL)
+ fatal( "not enough memory" );
+
+ return p;
+}
+
+#define xnew(p) (p) = xalloc(sizeof(*(p)))
+
+static void*
+xalloc0( size_t sz )
+{
+ void* p = xalloc(sz);
+ memset( p, 0, sz );
+ return p;
+}
+
+#define xnew0(p) (p) = xalloc0(sizeof(*(p)))
+
+#define xfree(p) (free((p)), (p) = NULL)
+
+static void*
+xrealloc( void* block, size_t size )
+{
+ void* p = realloc( block, size );
+
+ if (p == NULL && size > 0)
+ fatal( "not enough memory" );
+
+ return p;
+}
+
+#define xrenew(p,count) (p) = xrealloc((p),sizeof(*(p))*(count))
+
+static int
+hex2int( const uint8_t* data, int len )
+{
+ int result = 0;
+ while (len > 0) {
+ int c = *data++;
+ unsigned d;
+
+ result <<= 4;
+ do {
+ d = (unsigned)(c - '0');
+ if (d < 10)
+ break;
+
+ d = (unsigned)(c - 'a');
+ if (d < 6) {
+ d += 10;
+ break;
+ }
+
+ d = (unsigned)(c - 'A');
+ if (d < 6) {
+ d += 10;
+ break;
+ }
+
+ return -1;
+ }
+ while (0);
+
+ result |= d;
+ len -= 1;
+ }
+ return result;
+}
+
+
+static void
+int2hex( int value, uint8_t* to, int width )
+{
+ int nn = 0;
+ static const char hexchars[16] = "0123456789abcdef";
+
+ for ( --width; width >= 0; width--, nn++ ) {
+ to[nn] = hexchars[(value >> (width*4)) & 15];
+ }
+}
+
+static int
+fd_read(int fd, void* to, int len)
+{
+ int ret;
+
+ do {
+ ret = read(fd, to, len);
+ } while (ret < 0 && errno == EINTR);
+
+ return ret;
+}
+
+static int
+fd_write(int fd, const void* from, int len)
+{
+ int ret;
+
+ do {
+ ret = write(fd, from, len);
+ } while (ret < 0 && errno == EINTR);
+
+ return ret;
+}
+
+static void
+fd_setnonblock(int fd)
+{
+ int ret, flags;
+
+ do {
+ flags = fcntl(fd, F_GETFD);
+ } while (flags < 0 && errno == EINTR);
+
+ if (flags < 0) {
+ fatal( "%s: could not get flags for fd %d: %s",
+ __FUNCTION__, fd, strerror(errno) );
+ }
+
+ do {
+ ret = fcntl(fd, F_SETFD, flags | O_NONBLOCK);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0) {
+ fatal( "%s: could not set fd %d to non-blocking: %s",
+ __FUNCTION__, fd, strerror(errno) );
+ }
+}
+
+
+static int
+fd_accept(int fd)
+{
+ struct sockaddr from;
+ socklen_t fromlen = sizeof(from);
+ int ret;
+
+ do {
+ ret = accept(fd, &from, &fromlen);
+ } while (ret < 0 && errno == EINTR);
+
+ return ret;
+}
+
+/** FD EVENT LOOP
+ **/
+
+/* A Looper object is used to monitor activity on one or more
+ * file descriptors (e.g sockets).
+ *
+ * - call looper_add() to register a function that will be
+ * called when events happen on the file descriptor.
+ *
+ * - call looper_enable() or looper_disable() to enable/disable
+ * the set of monitored events for a given file descriptor.
+ *
+ * - call looper_del() to unregister a file descriptor.
+ * this does *not* close the file descriptor.
+ *
+ * Note that you can only provide a single function to handle
+ * all events related to a given file descriptor.
+
+ * You can call looper_enable/_disable/_del within a function
+ * callback.
+ */
+
+/* the current implementation uses Linux's epoll facility
+ * the event mask we use are simply combinations of EPOLLIN
+ * EPOLLOUT, EPOLLHUP and EPOLLERR
+ */
+#include <sys/epoll.h>
+
+#define MAX_CHANNELS 16
+#define MAX_EVENTS (MAX_CHANNELS+1) /* each channel + the serial fd */
+
+/* the event handler function type, 'user' is a user-specific
+ * opaque pointer passed to looper_add().
+ */
+typedef void (*EventFunc)( void* user, int events );
+
+/* bit flags for the LoopHook structure.
+ *
+ * HOOK_PENDING means that an event happened on the
+ * corresponding file descriptor.
+ *
+ * HOOK_CLOSING is used to delay-close monitored
+ * file descriptors.
+ */
+enum {
+ HOOK_PENDING = (1 << 0),
+ HOOK_CLOSING = (1 << 1),
+};
+
+/* A LoopHook structure is used to monitor a given
+ * file descriptor and record its event handler.
+ */
+typedef struct {
+ int fd;
+ int wanted; /* events we are monitoring */
+ int events; /* events that occured */
+ int state; /* see HOOK_XXX constants */
+ void* ev_user; /* user-provided handler parameter */
+ EventFunc ev_func; /* event handler callback */
+} LoopHook;
+
+/* Looper is the main object modeling a looper object
+ */
+typedef struct {
+ int epoll_fd;
+ int num_fds;
+ int max_fds;
+ struct epoll_event* events;
+ LoopHook* hooks;
+} Looper;
+
+/* initialize a looper object */
+static void
+looper_init( Looper* l )
+{
+ l->epoll_fd = epoll_create(4);
+ l->num_fds = 0;
+ l->max_fds = 0;
+ l->events = NULL;
+ l->hooks = NULL;
+}
+
+/* finalize a looper object */
+static void
+looper_done( Looper* l )
+{
+ xfree(l->events);
+ xfree(l->hooks);
+ l->max_fds = 0;
+ l->num_fds = 0;
+
+ close(l->epoll_fd);
+ l->epoll_fd = -1;
+}
+
+/* return the LoopHook corresponding to a given
+ * monitored file descriptor, or NULL if not found
+ */
+static LoopHook*
+looper_find( Looper* l, int fd )
+{
+ LoopHook* hook = l->hooks;
+ LoopHook* end = hook + l->num_fds;
+
+ for ( ; hook < end; hook++ ) {
+ if (hook->fd == fd)
+ return hook;
+ }
+ return NULL;
+}
+
+/* grow the arrays in the looper object */
+static void
+looper_grow( Looper* l )
+{
+ int old_max = l->max_fds;
+ int new_max = old_max + (old_max >> 1) + 4;
+ int n;
+
+ xrenew( l->events, new_max );
+ xrenew( l->hooks, new_max );
+ l->max_fds = new_max;
+
+ /* now change the handles to all events */
+ for (n = 0; n < l->num_fds; n++) {
+ struct epoll_event ev;
+ LoopHook* hook = l->hooks + n;
+
+ ev.events = hook->wanted;
+ ev.data.ptr = hook;
+ epoll_ctl( l->epoll_fd, EPOLL_CTL_MOD, hook->fd, &ev );
+ }
+}
+
+/* register a file descriptor and its event handler.
+ * no event mask will be enabled
+ */
+static void
+looper_add( Looper* l, int fd, EventFunc func, void* user )
+{
+ struct epoll_event ev;
+ LoopHook* hook;
+
+ if (l->num_fds >= l->max_fds)
+ looper_grow(l);
+
+ hook = l->hooks + l->num_fds;
+
+ hook->fd = fd;
+ hook->ev_user = user;
+ hook->ev_func = func;
+ hook->state = 0;
+ hook->wanted = 0;
+ hook->events = 0;
+
+ fd_setnonblock(fd);
+
+ ev.events = 0;
+ ev.data.ptr = hook;
+ epoll_ctl( l->epoll_fd, EPOLL_CTL_ADD, fd, &ev );
+
+ l->num_fds += 1;
+}
+
+/* unregister a file descriptor and its event handler
+ */
+static void
+looper_del( Looper* l, int fd )
+{
+ LoopHook* hook = looper_find( l, fd );
+
+ if (!hook) {
+ D( "%s: invalid fd: %d", __FUNCTION__, fd );
+ return;
+ }
+ /* don't remove the hook yet */
+ hook->state |= HOOK_CLOSING;
+
+ epoll_ctl( l->epoll_fd, EPOLL_CTL_DEL, fd, NULL );
+}
+
+/* enable monitoring of certain events for a file
+ * descriptor. This adds 'events' to the current
+ * event mask
+ */
+static void
+looper_enable( Looper* l, int fd, int events )
+{
+ LoopHook* hook = looper_find( l, fd );
+
+ if (!hook) {
+ D("%s: invalid fd: %d", __FUNCTION__, fd );
+ return;
+ }
+
+ if (events & ~hook->wanted) {
+ struct epoll_event ev;
+
+ hook->wanted |= events;
+ ev.events = hook->wanted;
+ ev.data.ptr = hook;
+
+ epoll_ctl( l->epoll_fd, EPOLL_CTL_MOD, fd, &ev );
+ }
+}
+
+/* disable monitoring of certain events for a file
+ * descriptor. This ignores events that are not
+ * currently enabled.
+ */
+static void
+looper_disable( Looper* l, int fd, int events )
+{
+ LoopHook* hook = looper_find( l, fd );
+
+ if (!hook) {
+ D("%s: invalid fd: %d", __FUNCTION__, fd );
+ return;
+ }
+
+ if (events & hook->wanted) {
+ struct epoll_event ev;
+
+ hook->wanted &= ~events;
+ ev.events = hook->wanted;
+ ev.data.ptr = hook;
+
+ epoll_ctl( l->epoll_fd, EPOLL_CTL_MOD, fd, &ev );
+ }
+}
+
+/* wait until an event occurs on one of the registered file
+ * descriptors. Only returns in case of error !!
+ */
+static void
+looper_loop( Looper* l )
+{
+ for (;;) {
+ int n, count;
+
+ do {
+ count = epoll_wait( l->epoll_fd, l->events, l->num_fds, -1 );
+ } while (count < 0 && errno == EINTR);
+
+ if (count < 0) {
+ D("%s: error: %s", __FUNCTION__, strerror(errno) );
+ return;
+ }
+
+ if (count == 0) {
+ D("%s: huh ? epoll returned count=0", __FUNCTION__);
+ continue;
+ }
+
+ /* mark all pending hooks */
+ for (n = 0; n < count; n++) {
+ LoopHook* hook = l->events[n].data.ptr;
+ hook->state = HOOK_PENDING;
+ hook->events = l->events[n].events;
+ }
+
+ /* execute hook callbacks. this may change the 'hooks'
+ * and 'events' array, as well as l->num_fds, so be careful */
+ for (n = 0; n < l->num_fds; n++) {
+ LoopHook* hook = l->hooks + n;
+ if (hook->state & HOOK_PENDING) {
+ hook->state &= ~HOOK_PENDING;
+ hook->ev_func( hook->ev_user, hook->events );
+ }
+ }
+
+ /* now remove all the hooks that were closed by
+ * the callbacks */
+ for (n = 0; n < l->num_fds;) {
+ struct epoll_event ev;
+ LoopHook* hook = l->hooks + n;
+
+ if (!(hook->state & HOOK_CLOSING)) {
+ n++;
+ continue;
+ }
+
+ hook[0] = l->hooks[l->num_fds-1];
+ l->num_fds -= 1;
+ ev.events = hook->wanted;
+ ev.data.ptr = hook;
+ epoll_ctl( l->epoll_fd, EPOLL_CTL_MOD, hook->fd, &ev );
+ }
+ }
+}
+
+#if T_ACTIVE
+char*
+quote( const void* data, int len )
+{
+ const char* p = data;
+ const char* end = p + len;
+ int count = 0;
+ int phase = 0;
+ static char* buff = NULL;
+
+ for (phase = 0; phase < 2; phase++) {
+ if (phase != 0) {
+ xfree(buff);
+ buff = xalloc(count+1);
+ }
+ count = 0;
+ for (p = data; p < end; p++) {
+ int c = *p;
+
+ if (c == '\\') {
+ if (phase != 0) {
+ buff[count] = buff[count+1] = '\\';
+ }
+ count += 2;
+ continue;
+ }
+
+ if (c >= 32 && c < 127) {
+ if (phase != 0)
+ buff[count] = c;
+ count += 1;
+ continue;
+ }
+
+
+ if (c == '\t') {
+ if (phase != 0) {
+ memcpy(buff+count, "<TAB>", 5);
+ }
+ count += 5;
+ continue;
+ }
+ if (c == '\n') {
+ if (phase != 0) {
+ memcpy(buff+count, "<LN>", 4);
+ }
+ count += 4;
+ continue;
+ }
+ if (c == '\r') {
+ if (phase != 0) {
+ memcpy(buff+count, "<CR>", 4);
+ }
+ count += 4;
+ continue;
+ }
+
+ if (phase != 0) {
+ buff[count+0] = '\\';
+ buff[count+1] = 'x';
+ buff[count+2] = "0123456789abcdef"[(c >> 4) & 15];
+ buff[count+3] = "0123456789abcdef"[ (c) & 15];
+ }
+ count += 4;
+ }
+ }
+ buff[count] = 0;
+ return buff;
+}
+#endif /* T_ACTIVE */
+
+/** PACKETS
+ **
+ ** We need a way to buffer data before it can be sent to the
+ ** corresponding file descriptor. We use linked list of Packet
+ ** objects to do this.
+ **/
+
+typedef struct Packet Packet;
+
+#define MAX_PAYLOAD 4000
+
+struct Packet {
+ Packet* next;
+ int len;
+ int channel;
+ uint8_t data[ MAX_PAYLOAD ];
+};
+
+/* we expect to alloc/free a lot of packets during
+ * operations so use a single linked list of free packets
+ * to keep things speedy and simple.
+ */
+static Packet* _free_packets;
+
+/* Allocate a packet */
+static Packet*
+packet_alloc(void)
+{
+ Packet* p = _free_packets;
+ if (p != NULL) {
+ _free_packets = p->next;
+ } else {
+ xnew(p);
+ }
+ p->next = NULL;
+ p->len = 0;
+ p->channel = -1;
+ return p;
+}
+
+/* Release a packet. This takes the address of a packet
+ * pointer that will be set to NULL on exit (avoids
+ * referencing dangling pointers in case of bugs)
+ */
+static void
+packet_free( Packet* *ppacket )
+{
+ Packet* p = *ppacket;
+ if (p) {
+ p->next = _free_packets;
+ _free_packets = p;
+ *ppacket = NULL;
+ }
+}
+
+/** PACKET RECEIVER
+ **
+ ** Simple abstraction for something that can receive a packet
+ ** from a FDHandler (see below) or something else.
+ **
+ ** Send a packet to it with 'receiver_post'
+ **
+ ** Call 'receiver_close' to indicate that the corresponding
+ ** packet source was closed.
+ **/
+
+typedef void (*PostFunc) ( void* user, Packet* p );
+typedef void (*CloseFunc)( void* user );
+
+typedef struct {
+ PostFunc post;
+ CloseFunc close;
+ void* user;
+} Receiver;
+
+/* post a packet to a receiver. Note that this transfers
+ * ownership of the packet to the receiver.
+ */
+static __inline__ void
+receiver_post( Receiver* r, Packet* p )
+{
+ if (r->post)
+ r->post( r->user, p );
+ else
+ packet_free(&p);
+}
+
+/* tell a receiver the packet source was closed.
+ * this will also prevent further posting to the
+ * receiver.
+ */
+static __inline__ void
+receiver_close( Receiver* r )
+{
+ if (r->close) {
+ r->close( r->user );
+ r->close = NULL;
+ }
+ r->post = NULL;
+}
+
+
+/** FD HANDLERS
+ **
+ ** these are smart listeners that send incoming packets to a receiver
+ ** and can queue one or more outgoing packets and send them when
+ ** possible to the FD.
+ **
+ ** note that we support clean shutdown of file descriptors,
+ ** i.e. we try to send all outgoing packets before destroying
+ ** the FDHandler.
+ **/
+
+typedef struct FDHandler FDHandler;
+typedef struct FDHandlerList FDHandlerList;
+
+struct FDHandler {
+ int fd;
+ FDHandlerList* list;
+ char closing;
+ Receiver receiver[1];
+
+ /* queue of outgoing packets */
+ int out_pos;
+ Packet* out_first;
+ Packet** out_ptail;
+
+ FDHandler* next;
+ FDHandler** pref;
+
+};
+
+struct FDHandlerList {
+ /* the looper that manages the fds */
+ Looper* looper;
+
+ /* list of active FDHandler objects */
+ FDHandler* active;
+
+ /* list of closing FDHandler objects.
+ * these are waiting to push their
+ * queued packets to the fd before
+ * freeing themselves.
+ */
+ FDHandler* closing;
+
+};
+
+/* remove a FDHandler from its current list */
+static void
+fdhandler_remove( FDHandler* f )
+{
+ f->pref[0] = f->next;
+ if (f->next)
+ f->next->pref = f->pref;
+}
+
+/* add a FDHandler to a given list */
+static void
+fdhandler_prepend( FDHandler* f, FDHandler** list )
+{
+ f->next = list[0];
+ f->pref = list;
+ list[0] = f;
+ if (f->next)
+ f->next->pref = &f->next;
+}
+
+/* initialize a FDHandler list */
+static void
+fdhandler_list_init( FDHandlerList* list, Looper* looper )
+{
+ list->looper = looper;
+ list->active = NULL;
+ list->closing = NULL;
+}
+
+
+/* close a FDHandler (and free it). Note that this will not
+ * perform a graceful shutdown, i.e. all packets in the
+ * outgoing queue will be immediately free.
+ *
+ * this *will* notify the receiver that the file descriptor
+ * was closed.
+ *
+ * you should call fdhandler_shutdown() if you want to
+ * notify the FDHandler that its packet source is closed.
+ */
+static void
+fdhandler_close( FDHandler* f )
+{
+ /* notify receiver */
+ receiver_close(f->receiver);
+
+ /* remove the handler from its list */
+ fdhandler_remove(f);
+
+ /* get rid of outgoing packet queue */
+ if (f->out_first != NULL) {
+ Packet* p;
+ while ((p = f->out_first) != NULL) {
+ f->out_first = p->next;
+ packet_free(&p);
+ }
+ }
+
+ /* get rid of file descriptor */
+ if (f->fd >= 0) {
+ looper_del( f->list->looper, f->fd );
+ close(f->fd);
+ f->fd = -1;
+ }
+
+ f->list = NULL;
+ xfree(f);
+}
+
+/* Ask the FDHandler to cleanly shutdown the connection,
+ * i.e. send any pending outgoing packets then auto-free
+ * itself.
+ */
+static void
+fdhandler_shutdown( FDHandler* f )
+{
+ /* prevent later fdhandler_close() to
+ * call the receiver's close.
+ */
+ f->receiver->close = NULL;
+
+ if (f->out_first != NULL && !f->closing)
+ {
+ /* move the handler to the 'closing' list */
+ f->closing = 1;
+ fdhandler_remove(f);
+ fdhandler_prepend(f, &f->list->closing);
+ return;
+ }
+
+ fdhandler_close(f);
+}
+
+/* Enqueue a new packet that the FDHandler will
+ * send through its file descriptor.
+ */
+static void
+fdhandler_enqueue( FDHandler* f, Packet* p )
+{
+ Packet* first = f->out_first;
+
+ p->next = NULL;
+ f->out_ptail[0] = p;
+ f->out_ptail = &p->next;
+
+ if (first == NULL) {
+ f->out_pos = 0;
+ looper_enable( f->list->looper, f->fd, EPOLLOUT );
+ }
+}
+
+
+/* FDHandler file descriptor event callback for read/write ops */
+static void
+fdhandler_event( FDHandler* f, int events )
+{
+ int len;
+
+ /* in certain cases, it's possible to have both EPOLLIN and
+ * EPOLLHUP at the same time. This indicates that there is incoming
+ * data to read, but that the connection was nonetheless closed
+ * by the sender. Be sure to read the data before closing
+ * the receiver to avoid packet loss.
+ */
+
+ if (events & EPOLLIN) {
+ Packet* p = packet_alloc();
+ int len;
+
+ if ((len = fd_read(f->fd, p->data, MAX_PAYLOAD)) < 0) {
+ D("%s: can't recv: %s", __FUNCTION__, strerror(errno));
+ packet_free(&p);
+ } else if (len > 0) {
+ p->len = len;
+ p->channel = -101; /* special debug value, not used */
+ receiver_post( f->receiver, p );
+ }
+ }
+
+ if (events & (EPOLLHUP|EPOLLERR)) {
+ /* disconnection */
+ D("%s: disconnect on fd %d", __FUNCTION__, f->fd);
+ fdhandler_close(f);
+ return;
+ }
+
+ if (events & EPOLLOUT && f->out_first) {
+ Packet* p = f->out_first;
+ int avail, len;
+
+ avail = p->len - f->out_pos;
+ if ((len = fd_write(f->fd, p->data + f->out_pos, avail)) < 0) {
+ D("%s: can't send: %s", __FUNCTION__, strerror(errno));
+ } else {
+ f->out_pos += len;
+ if (f->out_pos >= p->len) {
+ f->out_pos = 0;
+ f->out_first = p->next;
+ packet_free(&p);
+ if (f->out_first == NULL) {
+ f->out_ptail = &f->out_first;
+ looper_disable( f->list->looper, f->fd, EPOLLOUT );
+ }
+ }
+ }
+ }
+}
+
+
+/* Create a new FDHandler that monitors read/writes */
+static FDHandler*
+fdhandler_new( int fd,
+ FDHandlerList* list,
+ Receiver* receiver )
+{
+ FDHandler* f = xalloc0(sizeof(*f));
+
+ f->fd = fd;
+ f->list = list;
+ f->receiver[0] = receiver[0];
+ f->out_first = NULL;
+ f->out_ptail = &f->out_first;
+ f->out_pos = 0;
+
+ fdhandler_prepend(f, &list->active);
+
+ looper_add( list->looper, fd, (EventFunc) fdhandler_event, f );
+ looper_enable( list->looper, fd, EPOLLIN );
+
+ return f;
+}
+
+
+/* event callback function to monitor accepts() on server sockets.
+ * the convention used here is that the receiver will receive a
+ * dummy packet with the new client socket in p->channel
+ */
+static void
+fdhandler_accept_event( FDHandler* f, int events )
+{
+ if (events & EPOLLIN) {
+ /* this is an accept - send a dummy packet to the receiver */
+ Packet* p = packet_alloc();
+
+ D("%s: accepting on fd %d", __FUNCTION__, f->fd);
+ p->data[0] = 1;
+ p->len = 1;
+ p->channel = fd_accept(f->fd);
+ if (p->channel < 0) {
+ D("%s: accept failed ?: %s", __FUNCTION__, strerror(errno));
+ packet_free(&p);
+ return;
+ }
+ receiver_post( f->receiver, p );
+ }
+
+ if (events & (EPOLLHUP|EPOLLERR)) {
+ /* disconnecting !! */
+ D("%s: closing accept fd %d", __FUNCTION__, f->fd);
+ fdhandler_close(f);
+ return;
+ }
+}
+
+
+/* Create a new FDHandler used to monitor new connections on a
+ * server socket. The receiver must expect the new connection
+ * fd in the 'channel' field of a dummy packet.
+ */
+static FDHandler*
+fdhandler_new_accept( int fd,
+ FDHandlerList* list,
+ Receiver* receiver )
+{
+ FDHandler* f = xalloc0(sizeof(*f));
+
+ f->fd = fd;
+ f->list = list;
+ f->receiver[0] = receiver[0];
+
+ fdhandler_prepend(f, &list->active);
+
+ looper_add( list->looper, fd, (EventFunc) fdhandler_accept_event, f );
+ looper_enable( list->looper, fd, EPOLLIN );
+ listen( fd, 5 );
+
+ return f;
+}
+
+/** SERIAL CONNECTION STATE
+ **
+ ** The following is used to handle the framing protocol
+ ** used on the serial port connection.
+ **/
+
+/* each packet is made of a 6 byte header followed by a payload
+ * the header looks like:
+ *
+ * offset size description
+ * 0 2 a 2-byte hex string for the channel number
+ * 4 4 a 4-char hex string for the size of the payload
+ * 6 n the payload itself
+ */
+#define HEADER_SIZE 6
+#define CHANNEL_OFFSET 0
+#define LENGTH_OFFSET 2
+#define CHANNEL_SIZE 2
+#define LENGTH_SIZE 4
+
+#define CHANNEL_CONTROL 0
+
+/* The Serial object receives data from the serial port,
+ * extracts the payload size and channel index, then sends
+ * the resulting messages as a packet to a generic receiver.
+ *
+ * You can also use serial_send to send a packet through
+ * the serial port.
+ */
+typedef struct Serial {
+ FDHandler* fdhandler; /* used to monitor serial port fd */
+ Receiver receiver[1]; /* send payload there */
+ int in_len; /* current bytes in input packet */
+ int in_datalen; /* payload size, or 0 when reading header */
+ int in_channel; /* extracted channel number */
+ Packet* in_packet; /* used to read incoming packets */
+} Serial;
+
+
+/* a callback called when the serial port's fd is closed */
+static void
+serial_fd_close( Serial* s )
+{
+ fatal("unexpected serial port close !!");
+}
+
+static void
+serial_dump( Packet* p, const char* funcname )
+{
+ T("%s: %03d bytes: '%s'",
+ funcname, p->len, quote(p->data, p->len));
+}
+
+/* a callback called when a packet arrives from the serial port's FDHandler.
+ *
+ * This will essentially parse the header, extract the channel number and
+ * the payload size and store them in 'in_datalen' and 'in_channel'.
+ *
+ * After that, the payload is sent to the receiver once completed.
+ */
+static void
+serial_fd_receive( Serial* s, Packet* p )
+{
+ int rpos = 0, rcount = p->len;
+ Packet* inp = s->in_packet;
+ int inpos = s->in_len;
+
+ serial_dump( p, __FUNCTION__ );
+
+ while (rpos < rcount)
+ {
+ int avail = rcount - rpos;
+
+ /* first, try to read the header */
+ if (s->in_datalen == 0) {
+ int wanted = HEADER_SIZE - inpos;
+ if (avail > wanted)
+ avail = wanted;
+
+ memcpy( inp->data + inpos, p->data + rpos, avail );
+ inpos += avail;
+ rpos += avail;
+
+ if (inpos == HEADER_SIZE) {
+ s->in_datalen = hex2int( inp->data + LENGTH_OFFSET, LENGTH_SIZE );
+ s->in_channel = hex2int( inp->data + CHANNEL_OFFSET, CHANNEL_SIZE );
+
+ if (s->in_datalen <= 0) {
+ D("ignoring %s packet from serial port",
+ s->in_datalen ? "empty" : "malformed");
+ s->in_datalen = 0;
+ }
+
+ //D("received %d bytes packet for channel %d", s->in_datalen, s->in_channel);
+ inpos = 0;
+ }
+ }
+ else /* then, populate the packet itself */
+ {
+ int wanted = s->in_datalen - inpos;
+
+ if (avail > wanted)
+ avail = wanted;
+
+ memcpy( inp->data + inpos, p->data + rpos, avail );
+ inpos += avail;
+ rpos += avail;
+
+ if (inpos == s->in_datalen) {
+ if (s->in_channel < 0) {
+ D("ignoring %d bytes addressed to channel %d",
+ inpos, s->in_channel);
+ } else {
+ inp->len = inpos;
+ inp->channel = s->in_channel;
+ receiver_post( s->receiver, inp );
+ s->in_packet = inp = packet_alloc();
+ }
+ s->in_datalen = 0;
+ inpos = 0;
+ }
+ }
+ }
+ s->in_len = inpos;
+ packet_free(&p);
+}
+
+
+/* send a packet to the serial port.
+ * this assumes that p->len and p->channel contain the payload's
+ * size and channel and will add the appropriate header.
+ */
+static void
+serial_send( Serial* s, Packet* p )
+{
+ Packet* h = packet_alloc();
+
+ //D("sending to serial %d bytes from channel %d: '%.*s'", p->len, p->channel, p->len, p->data);
+
+ /* insert a small header before this packet */
+ h->len = HEADER_SIZE;
+ int2hex( p->len, h->data + LENGTH_OFFSET, LENGTH_SIZE );
+ int2hex( p->channel, h->data + CHANNEL_OFFSET, CHANNEL_SIZE );
+
+ serial_dump( h, __FUNCTION__ );
+ serial_dump( p, __FUNCTION__ );
+
+ fdhandler_enqueue( s->fdhandler, h );
+ fdhandler_enqueue( s->fdhandler, p );
+}
+
+
+/* initialize serial reader */
+static void
+serial_init( Serial* s,
+ int fd,
+ FDHandlerList* list,
+ Receiver* receiver )
+{
+ Receiver recv;
+
+ recv.user = s;
+ recv.post = (PostFunc) serial_fd_receive;
+ recv.close = (CloseFunc) serial_fd_close;
+
+ s->receiver[0] = receiver[0];
+
+ s->fdhandler = fdhandler_new( fd, list, &recv );
+ s->in_len = 0;
+ s->in_datalen = 0;
+ s->in_channel = 0;
+ s->in_packet = packet_alloc();
+}
+
+
+/** CLIENTS
+ **/
+
+typedef struct Client Client;
+typedef struct Multiplexer Multiplexer;
+
+/* A Client object models a single qemud client socket
+ * connection in the emulated system.
+ *
+ * the client first sends the name of the system service
+ * it wants to contact (no framing), then waits for a 2
+ * byte answer from qemud.
+ *
+ * the answer is either "OK" or "KO" to indicate
+ * success or failure.
+ *
+ * In case of success, the client can send messages
+ * to the service.
+ *
+ * In case of failure, it can disconnect or try sending
+ * the name of another service.
+ */
+struct Client {
+ Client* next;
+ Client** pref;
+ int channel;
+ char registered;
+ FDHandler* fdhandler;
+ Multiplexer* multiplexer;
+};
+
+struct Multiplexer {
+ Client* clients;
+ int last_channel;
+ Serial serial[1];
+ Looper looper[1];
+ FDHandlerList fdhandlers[1];
+};
+
+
+static int multiplexer_open_channel( Multiplexer* mult, Packet* p );
+static void multiplexer_close_channel( Multiplexer* mult, int channel );
+static void multiplexer_serial_send( Multiplexer* mult, int channel, Packet* p );
+
+static void
+client_dump( Client* c, Packet* p, const char* funcname )
+{
+ T("%s: client %p (%d): %3d bytes: '%s'",
+ funcname, c, c->fdhandler->fd,
+ p->len, quote(p->data, p->len));
+}
+
+/* destroy a client */
+static void
+client_free( Client* c )
+{
+ /* remove from list */
+ c->pref[0] = c->next;
+ if (c->next)
+ c->next->pref = c->pref;
+
+ c->channel = -1;
+ c->registered = 0;
+
+ /* gently ask the FDHandler to shutdown to
+ * avoid losing queued outgoing packets */
+ if (c->fdhandler != NULL) {
+ fdhandler_shutdown(c->fdhandler);
+ c->fdhandler = NULL;
+ }
+
+ xfree(c);
+}
+
+
+/* a function called when a client socket receives data */
+static void
+client_fd_receive( Client* c, Packet* p )
+{
+ client_dump(c, p, __FUNCTION__);
+
+ if (c->registered) {
+ /* the client is registered, just send the
+ * data through the serial port
+ */
+ multiplexer_serial_send(c->multiplexer, c->channel, p);
+ return;
+ }
+
+ if (c->channel > 0) {
+ /* the client is waiting registration results.
+ * this should not happen because the client
+ * should wait for our 'ok' or 'ko'.
+ * close the connection.
+ */
+ D("%s: bad client sending data before end of registration",
+ __FUNCTION__);
+ BAD_CLIENT:
+ packet_free(&p);
+ client_free(c);
+ return;
+ }
+
+ /* the client hasn't registered a service yet,
+ * so this must be the name of a service, call
+ * the multiplexer to start registration for
+ * it.
+ */
+ D("%s: attempting registration for service '%.*s'",
+ __FUNCTION__, p->len, p->data);
+ c->channel = multiplexer_open_channel(c->multiplexer, p);
+ if (c->channel < 0) {
+ D("%s: service name too long", __FUNCTION__);
+ goto BAD_CLIENT;
+ }
+ D("%s: -> received channel id %d", __FUNCTION__, c->channel);
+ packet_free(&p);
+}
+
+
+/* a function called when the client socket is closed. */
+static void
+client_fd_close( Client* c )
+{
+ T("%s: client %p (%d)", __FUNCTION__, c, c->fdhandler->fd);
+
+ /* no need to shutdown the FDHandler */
+ c->fdhandler = NULL;
+
+ /* tell the emulator we're out */
+ if (c->channel > 0)
+ multiplexer_close_channel(c->multiplexer, c->channel);
+
+ /* free the client */
+ client_free(c);
+}
+
+/* a function called when the multiplexer received a registration
+ * response from the emulator for a given client.
+ */
+static void
+client_registration( Client* c, int registered )
+{
+ Packet* p = packet_alloc();
+
+ /* sends registration status to client */
+ if (!registered) {
+ D("%s: registration failed for client %d", __FUNCTION__, c->channel);
+ memcpy( p->data, "KO", 2 );
+ p->len = 2;
+ } else {
+ D("%s: registration succeeded for client %d", __FUNCTION__, c->channel);
+ memcpy( p->data, "OK", 2 );
+ p->len = 2;
+ }
+ client_dump(c, p, __FUNCTION__);
+ fdhandler_enqueue(c->fdhandler, p);
+
+ /* now save registration state
+ */
+ c->registered = registered;
+ if (!registered) {
+ /* allow the client to try registering another service */
+ c->channel = -1;
+ }
+}
+
+/* send data to a client */
+static void
+client_send( Client* c, Packet* p )
+{
+ client_dump(c, p, __FUNCTION__);
+ fdhandler_enqueue(c->fdhandler, p);
+}
+
+
+/* Create new client socket handler */
+static Client*
+client_new( Multiplexer* mult,
+ int fd,
+ FDHandlerList* pfdhandlers,
+ Client** pclients )
+{
+ Client* c;
+ Receiver recv;
+
+ xnew(c);
+
+ c->multiplexer = mult;
+ c->next = NULL;
+ c->pref = &c->next;
+ c->channel = -1;
+ c->registered = 0;
+
+ recv.user = c;
+ recv.post = (PostFunc) client_fd_receive;
+ recv.close = (CloseFunc) client_fd_close;
+
+ c->fdhandler = fdhandler_new( fd, pfdhandlers, &recv );
+
+ /* add to client list */
+ c->next = *pclients;
+ c->pref = pclients;
+ *pclients = c;
+ if (c->next)
+ c->next->pref = &c->next;
+
+ return c;
+}
+
+/** GLOBAL MULTIPLEXER
+ **/
+
+/* find a client by its channel */
+static Client*
+multiplexer_find_client( Multiplexer* mult, int channel )
+{
+ Client* c = mult->clients;
+
+ for ( ; c != NULL; c = c->next ) {
+ if (c->channel == channel)
+ return c;
+ }
+ return NULL;
+}
+
+/* handle control messages coming from the serial port
+ * on CONTROL_CHANNEL.
+ */
+static void
+multiplexer_handle_control( Multiplexer* mult, Packet* p )
+{
+ /* connection registration success */
+ if (p->len == 13 && !memcmp(p->data, "ok:connect:", 11)) {
+ int channel = hex2int(p->data+11, 2);
+ Client* client = multiplexer_find_client(mult, channel);
+
+ /* note that 'client' can be NULL if the corresponding
+ * socket was closed before the emulator response arrived.
+ */
+ if (client != NULL) {
+ client_registration(client, 1);
+ } else {
+ D("%s: NULL client: '%.*s'", __FUNCTION__, p->len, p->data+11);
+ }
+ goto EXIT;
+ }
+
+ /* connection registration failure */
+ if (p->len == 13 && !memcmp(p->data, "ko:connect:",11)) {
+ int channel = hex2int(p->data+11, 2);
+ Client* client = multiplexer_find_client(mult, channel);
+
+ if (client != NULL)
+ client_registration(client, 0);
+
+ goto EXIT;
+ }
+
+ /* emulator-induced client disconnection */
+ if (p->len == 13 && !memcmp(p->data, "disconnect:",11)) {
+ int channel = hex2int(p->data+11, 2);
+ Client* client = multiplexer_find_client(mult, channel);
+
+ if (client != NULL)
+ client_free(client);
+
+ goto EXIT;
+ }
+
+ /* A message that begins with "X00" is a probe sent by
+ * the emulator used to detect which version of qemud it runs
+ * against (in order to detect 1.0/1.1 system images. Just
+ * silently ignore it there instead of printing an error
+ * message.
+ */
+ if (p->len >= 3 && !memcmp(p->data,"X00",3)) {
+ goto EXIT;
+ }
+
+ D("%s: unknown control message (%d bytes): '%.*s'",
+ __FUNCTION__, p->len, p->len, p->data);
+
+EXIT:
+ packet_free(&p);
+}
+
+/* a function called when an incoming packet comes from the serial port */
+static void
+multiplexer_serial_receive( Multiplexer* mult, Packet* p )
+{
+ Client* client;
+
+ T("%s: channel=%d '%.*s'", __FUNCTION__, p->channel, p->len, p->data);
+
+ if (p->channel == CHANNEL_CONTROL) {
+ multiplexer_handle_control(mult, p);
+ return;
+ }
+
+ client = multiplexer_find_client(mult, p->channel);
+ if (client != NULL) {
+ client_send(client, p);
+ return;
+ }
+
+ D("%s: discarding packet for unknown channel %d", __FUNCTION__, p->channel);
+ packet_free(&p);
+}
+
+/* a function called when the serial reader closes */
+static void
+multiplexer_serial_close( Multiplexer* mult )
+{
+ fatal("unexpected close of serial reader");
+}
+
+/* a function called to send a packet to the serial port */
+static void
+multiplexer_serial_send( Multiplexer* mult, int channel, Packet* p )
+{
+ p->channel = channel;
+ serial_send( mult->serial, p );
+}
+
+
+
+/* a function used by a client to allocate a new channel id and
+ * ask the emulator to open it. 'service' must be a packet containing
+ * the name of the service in its payload.
+ *
+ * returns -1 if the service name is too long.
+ *
+ * notice that client_registration() will be called later when
+ * the answer arrives.
+ */
+static int
+multiplexer_open_channel( Multiplexer* mult, Packet* service )
+{
+ Packet* p = packet_alloc();
+ int len, channel;
+
+ /* find a free channel number, assume we don't have many
+ * clients here. */
+ {
+ Client* c;
+ TRY_AGAIN:
+ channel = (++mult->last_channel) & 0xff;
+
+ for (c = mult->clients; c != NULL; c = c->next)
+ if (c->channel == channel)
+ goto TRY_AGAIN;
+ }
+
+ len = snprintf((char*)p->data, sizeof p->data, "connect:%.*s:%02x", service->len, service->data, channel);
+ if (len >= (int)sizeof(p->data)) {
+ D("%s: weird, service name too long (%d > %d)", __FUNCTION__, len, sizeof(p->data));
+ packet_free(&p);
+ return -1;
+ }
+ p->channel = CHANNEL_CONTROL;
+ p->len = len;
+
+ serial_send(mult->serial, p);
+ return channel;
+}
+
+/* used to tell the emulator a channel was closed by a client */
+static void
+multiplexer_close_channel( Multiplexer* mult, int channel )
+{
+ Packet* p = packet_alloc();
+ int len = snprintf((char*)p->data, sizeof(p->data), "disconnect:%02x", channel);
+
+ if (len > (int)sizeof(p->data)) {
+ /* should not happen */
+ return;
+ }
+
+ p->channel = CHANNEL_CONTROL;
+ p->len = len;
+
+ serial_send(mult->serial, p);
+}
+
+/* this function is used when a new connection happens on the control
+ * socket.
+ */
+static void
+multiplexer_control_accept( Multiplexer* m, Packet* p )
+{
+ /* the file descriptor for the new socket connection is
+ * in p->channel. See fdhandler_accept_event() */
+ int fd = p->channel;
+ Client* client = client_new( m, fd, m->fdhandlers, &m->clients );
+
+ D("created client %p listening on fd %d", client, fd);
+
+ /* free dummy packet */
+ packet_free(&p);
+}
+
+static void
+multiplexer_control_close( Multiplexer* m )
+{
+ fatal("unexpected multiplexer control close");
+}
+
+static void
+multiplexer_init( Multiplexer* m, const char* serial_dev )
+{
+ int fd, control_fd;
+ Receiver recv;
+
+ /* initialize looper and fdhandlers list */
+ looper_init( m->looper );
+ fdhandler_list_init( m->fdhandlers, m->looper );
+
+ /* open the serial port */
+ do {
+ fd = open(serial_dev, O_RDWR);
+ } while (fd < 0 && errno == EINTR);
+
+ if (fd < 0) {
+ fatal( "%s: could not open '%s': %s", __FUNCTION__, serial_dev,
+ strerror(errno) );
+ }
+ // disable echo on serial lines
+ if ( !memcmp( serial_dev, "/dev/ttyS", 9 ) ) {
+ struct termios ios;
+ tcgetattr( fd, &ios );
+ ios.c_lflag = 0; /* disable ECHO, ICANON, etc... */
+ tcsetattr( fd, TCSANOW, &ios );
+ }
+
+ /* initialize the serial reader/writer */
+ recv.user = m;
+ recv.post = (PostFunc) multiplexer_serial_receive;
+ recv.close = (CloseFunc) multiplexer_serial_close;
+
+ serial_init( m->serial, fd, m->fdhandlers, &recv );
+
+ /* open the qemud control socket */
+ recv.user = m;
+ recv.post = (PostFunc) multiplexer_control_accept;
+ recv.close = (CloseFunc) multiplexer_control_close;
+
+ fd = android_get_control_socket(CONTROL_SOCKET_NAME);
+ if (fd < 0) {
+ fatal("couldn't get fd for control socket '%s'", CONTROL_SOCKET_NAME);
+ }
+
+ fdhandler_new_accept( fd, m->fdhandlers, &recv );
+
+ /* initialize clients list */
+ m->clients = NULL;
+}
+
+/** MAIN LOOP
+ **/
+
+static Multiplexer _multiplexer[1];
+
+int main( void )
+{
+ Multiplexer* m = _multiplexer;
+
+ /* extract the name of our serial device from the kernel
+ * boot options that are stored in /proc/cmdline
+ */
+#define KERNEL_OPTION "android.qemud="
+
+ {
+ char buff[1024];
+ int fd, len;
+ char* p;
+ char* q;
+
+ fd = open( "/proc/cmdline", O_RDONLY );
+ if (fd < 0) {
+ D("%s: can't open /proc/cmdline !!: %s", __FUNCTION__,
+ strerror(errno));
+ exit(1);
+ }
+
+ len = fd_read( fd, buff, sizeof(buff)-1 );
+ close(fd);
+ if (len < 0) {
+ D("%s: can't read /proc/cmdline: %s", __FUNCTION__,
+ strerror(errno));
+ exit(1);
+ }
+ buff[len] = 0;
+
+ p = strstr( buff, KERNEL_OPTION );
+ if (p == NULL) {
+ D("%s: can't find '%s' in /proc/cmdline",
+ __FUNCTION__, KERNEL_OPTION );
+ exit(1);
+ }
+
+ p += sizeof(KERNEL_OPTION)-1; /* skip option */
+ q = p;
+ while ( *q && *q != ' ' && *q != '\t' )
+ q += 1;
+
+ snprintf( buff, sizeof(buff), "/dev/%.*s", q-p, p );
+
+ multiplexer_init( m, buff );
+ }
+
+ D( "entering main loop");
+ looper_loop( m->looper );
+ D( "unexpected termination !!" );
+ return 0;
+}
diff --git a/emulator/sensors/Android.mk b/emulator/sensors/Android.mk
new file mode 100644
index 0000000..9b0e83d
--- /dev/null
+++ b/emulator/sensors/Android.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2009 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.
+
+
+# We're moving the emulator-specific platform libs to
+# development.git/tools/emulator/. The following test is to ensure
+# smooth builds even if the tree contains both versions.
+#
+ifndef BUILD_EMULATOR_SENSORS_MODULE
+BUILD_EMULATOR_SENSORS_MODULE := true
+
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TARGET_PRODUCT),sim)
+# HAL module implemenation, not prelinked and stored in
+# hw/<SENSORS_HARDWARE_MODULE_ID>.<ro.hardware>.so
+include $(CLEAR_VARS)
+LOCAL_PRELINK_MODULE := false
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_SHARED_LIBRARIES := liblog libcutils
+LOCAL_SRC_FILES := sensors_qemu.c
+LOCAL_MODULE := sensors.goldfish
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_SHARED_LIBRARY)
+endif
+
+endif # BUILD_EMULATOR_SENSORS_MODULE
diff --git a/emulator/sensors/sensors_qemu.c b/emulator/sensors/sensors_qemu.c
new file mode 100644
index 0000000..9a776c7
--- /dev/null
+++ b/emulator/sensors/sensors_qemu.c
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+/* this implements a sensors hardware library for the Android emulator.
+ * the following code should be built as a shared library that will be
+ * placed into /system/lib/hw/sensors.goldfish.so
+ *
+ * it will be loaded by the code in hardware/libhardware/hardware.c
+ * which is itself called from com_android_server_SensorService.cpp
+ */
+
+
+/* we connect with the emulator through the "sensors" qemud service
+ */
+#define SENSORS_SERVICE_NAME "sensors"
+
+#define LOG_TAG "QemuSensors"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <cutils/log.h>
+#include <cutils/native_handle.h>
+#include <cutils/sockets.h>
+#include <hardware/sensors.h>
+
+#if 0
+#define D(...) LOGD(__VA_ARGS__)
+#else
+#define D(...) ((void)0)
+#endif
+
+#define E(...) LOGE(__VA_ARGS__)
+
+#include <hardware/qemud.h>
+
+/** SENSOR IDS AND NAMES
+ **/
+
+#define MAX_NUM_SENSORS 5
+
+#define SUPPORTED_SENSORS ((1<<MAX_NUM_SENSORS)-1)
+
+#define ID_BASE SENSORS_HANDLE_BASE
+#define ID_ACCELERATION (ID_BASE+0)
+#define ID_MAGNETIC_FIELD (ID_BASE+1)
+#define ID_ORIENTATION (ID_BASE+2)
+#define ID_TEMPERATURE (ID_BASE+3)
+#define ID_PROXIMITY (ID_BASE+4)
+
+#define SENSORS_ACCELERATION (1 << ID_ACCELERATION)
+#define SENSORS_MAGNETIC_FIELD (1 << ID_MAGNETIC_FIELD)
+#define SENSORS_ORIENTATION (1 << ID_ORIENTATION)
+#define SENSORS_TEMPERATURE (1 << ID_TEMPERATURE)
+#define SENSORS_PROXIMITY (1 << ID_PROXIMITY)
+
+#define ID_CHECK(x) ((unsigned)((x)-ID_BASE) < MAX_NUM_SENSORS)
+
+#define SENSORS_LIST \
+ SENSOR_(ACCELERATION,"acceleration") \
+ SENSOR_(MAGNETIC_FIELD,"magnetic-field") \
+ SENSOR_(ORIENTATION,"orientation") \
+ SENSOR_(TEMPERATURE,"temperature") \
+ SENSOR_(PROXIMITY,"proximity") \
+
+static const struct {
+ const char* name;
+ int id; } _sensorIds[MAX_NUM_SENSORS] =
+{
+#define SENSOR_(x,y) { y, ID_##x },
+ SENSORS_LIST
+#undef SENSOR_
+};
+
+static const char*
+_sensorIdToName( int id )
+{
+ int nn;
+ for (nn = 0; nn < MAX_NUM_SENSORS; nn++)
+ if (id == _sensorIds[nn].id)
+ return _sensorIds[nn].name;
+ return "<UNKNOWN>";
+}
+
+static int
+_sensorIdFromName( const char* name )
+{
+ int nn;
+
+ if (name == NULL)
+ return -1;
+
+ for (nn = 0; nn < MAX_NUM_SENSORS; nn++)
+ if (!strcmp(name, _sensorIds[nn].name))
+ return _sensorIds[nn].id;
+
+ return -1;
+}
+
+/** SENSORS POLL DEVICE
+ **
+ ** This one is used to read sensor data from the hardware.
+ ** We implement this by simply reading the data from the
+ ** emulator through the QEMUD channel.
+ **/
+
+typedef struct SensorPoll {
+ struct sensors_poll_device_t device;
+ sensors_event_t sensors[MAX_NUM_SENSORS];
+ int events_fd;
+ uint32_t pendingSensors;
+ int64_t timeStart;
+ int64_t timeOffset;
+ int fd;
+ uint32_t active_sensors;
+} SensorPoll;
+
+/* this must return a file descriptor that will be used to read
+ * the sensors data (it is passed to data__data_open() below
+ */
+static native_handle_t*
+control__open_data_source(struct sensors_poll_device_t *dev)
+{
+ SensorPoll* ctl = (void*)dev;
+ native_handle_t* handle;
+
+ if (ctl->fd < 0) {
+ ctl->fd = qemud_channel_open(SENSORS_SERVICE_NAME);
+ }
+ D("%s: fd=%d", __FUNCTION__, ctl->fd);
+ handle = native_handle_create(1, 0);
+ handle->data[0] = dup(ctl->fd);
+ return handle;
+}
+
+static int
+control__activate(struct sensors_poll_device_t *dev,
+ int handle,
+ int enabled)
+{
+ SensorPoll* ctl = (void*)dev;
+ uint32_t mask, sensors, active, new_sensors, changed;
+ char command[128];
+ int ret;
+
+ D("%s: handle=%s (%d) fd=%d enabled=%d", __FUNCTION__,
+ _sensorIdToName(handle), handle, ctl->fd, enabled);
+
+ if (!ID_CHECK(handle)) {
+ E("%s: bad handle ID", __FUNCTION__);
+ return -1;
+ }
+
+ mask = (1<<handle);
+ sensors = enabled ? mask : 0;
+
+ active = ctl->active_sensors;
+ new_sensors = (active & ~mask) | (sensors & mask);
+ changed = active ^ new_sensors;
+
+ if (!changed)
+ return 0;
+
+ snprintf(command, sizeof command, "set:%s:%d",
+ _sensorIdToName(handle), enabled != 0);
+
+ if (ctl->fd < 0) {
+ ctl->fd = qemud_channel_open(SENSORS_SERVICE_NAME);
+ }
+
+ ret = qemud_channel_send(ctl->fd, command, -1);
+ if (ret < 0) {
+ E("%s: when sending command errno=%d: %s", __FUNCTION__, errno, strerror(errno));
+ return -1;
+ }
+ ctl->active_sensors = new_sensors;
+
+ return 0;
+}
+
+static int
+control__set_delay(struct sensors_poll_device_t *dev, int32_t ms)
+{
+ SensorPoll* ctl = (void*)dev;
+ char command[128];
+
+ D("%s: dev=%p delay-ms=%d", __FUNCTION__, dev, ms);
+
+ snprintf(command, sizeof command, "set-delay:%d", ms);
+
+ return qemud_channel_send(ctl->fd, command, -1);
+}
+
+static int
+control__close(struct hw_device_t *dev)
+{
+ SensorPoll* ctl = (void*)dev;
+ close(ctl->fd);
+ free(ctl);
+ return 0;
+}
+
+/* return the current time in nanoseconds */
+static int64_t
+data__now_ns(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
+}
+
+static int
+data__data_open(struct sensors_poll_device_t *dev, native_handle_t* handle)
+{
+ SensorPoll* data = (void*)dev;
+ int i;
+ D("%s: dev=%p fd=%d", __FUNCTION__, dev, handle->data[0]);
+ memset(&data->sensors, 0, sizeof(data->sensors));
+
+ for (i=0 ; i<MAX_NUM_SENSORS ; i++) {
+ data->sensors[i].acceleration.status = SENSOR_STATUS_ACCURACY_HIGH;
+ }
+ data->pendingSensors = 0;
+ data->timeStart = 0;
+ data->timeOffset = 0;
+
+ data->events_fd = dup(handle->data[0]);
+ D("%s: dev=%p fd=%d (was %d)", __FUNCTION__, dev, data->events_fd, handle->data[0]);
+ native_handle_close(handle);
+ native_handle_delete(handle);
+ return 0;
+}
+
+static int
+data__data_close(struct sensors_poll_device_t *dev)
+{
+ SensorPoll* data = (void*)dev;
+ D("%s: dev=%p", __FUNCTION__, dev);
+ if (data->events_fd >= 0) {
+ close(data->events_fd);
+ data->events_fd = -1;
+ }
+ return 0;
+}
+
+static int
+pick_sensor(SensorPoll* data,
+ sensors_event_t* values)
+{
+ uint32_t mask = SUPPORTED_SENSORS;
+ while (mask) {
+ uint32_t i = 31 - __builtin_clz(mask);
+ mask &= ~(1<<i);
+ if (data->pendingSensors & (1<<i)) {
+ data->pendingSensors &= ~(1<<i);
+ *values = data->sensors[i];
+ values->sensor = i;
+ values->version = sizeof(*values);
+
+ D("%s: %d [%f, %f, %f]", __FUNCTION__,
+ i,
+ values->data[0],
+ values->data[1],
+ values->data[2]);
+ return i;
+ }
+ }
+ LOGE("No sensor to return!!! pendingSensors=%08x", data->pendingSensors);
+ // we may end-up in a busy loop, slow things down, just in case.
+ usleep(100000);
+ return -EINVAL;
+}
+
+static int
+data__poll(struct sensors_poll_device_t *dev, sensors_event_t* values)
+{
+ SensorPoll* data = (void*)dev;
+ int fd = data->events_fd;
+
+ D("%s: data=%p", __FUNCTION__, dev);
+
+ // there are pending sensors, returns them now...
+ if (data->pendingSensors) {
+ return pick_sensor(data, values);
+ }
+
+ // wait until we get a complete event for an enabled sensor
+ uint32_t new_sensors = 0;
+
+ while (1) {
+ /* read the next event */
+ char buff[256];
+ int len = qemud_channel_recv(data->events_fd, buff, sizeof buff-1);
+ float params[3];
+ int64_t event_time;
+
+ if (len < 0) {
+ E("%s: len=%d, errno=%d: %s", __FUNCTION__, len, errno, strerror(errno));
+ return -errno;
+ }
+
+ buff[len] = 0;
+
+ /* "wake" is sent from the emulator to exit this loop. */
+ if (!strcmp((const char*)data, "wake")) {
+ return 0x7FFFFFFF;
+ }
+
+ /* "acceleration:<x>:<y>:<z>" corresponds to an acceleration event */
+ if (sscanf(buff, "acceleration:%g:%g:%g", params+0, params+1, params+2) == 3) {
+ new_sensors |= SENSORS_ACCELERATION;
+ data->sensors[ID_ACCELERATION].acceleration.x = params[0];
+ data->sensors[ID_ACCELERATION].acceleration.y = params[1];
+ data->sensors[ID_ACCELERATION].acceleration.z = params[2];
+ continue;
+ }
+
+ /* "orientation:<azimuth>:<pitch>:<roll>" is sent when orientation changes */
+ if (sscanf(buff, "orientation:%g:%g:%g", params+0, params+1, params+2) == 3) {
+ new_sensors |= SENSORS_ORIENTATION;
+ data->sensors[ID_ORIENTATION].orientation.azimuth = params[0];
+ data->sensors[ID_ORIENTATION].orientation.pitch = params[1];
+ data->sensors[ID_ORIENTATION].orientation.roll = params[2];
+ continue;
+ }
+
+ /* "magnetic:<x>:<y>:<z>" is sent for the params of the magnetic field */
+ if (sscanf(buff, "magnetic:%g:%g:%g", params+0, params+1, params+2) == 3) {
+ new_sensors |= SENSORS_MAGNETIC_FIELD;
+ data->sensors[ID_MAGNETIC_FIELD].magnetic.x = params[0];
+ data->sensors[ID_MAGNETIC_FIELD].magnetic.y = params[1];
+ data->sensors[ID_MAGNETIC_FIELD].magnetic.z = params[2];
+ continue;
+ }
+
+ /* "temperature:<celsius>" */
+ if (sscanf(buff, "temperature:%g", params+0) == 2) {
+ new_sensors |= SENSORS_TEMPERATURE;
+ data->sensors[ID_TEMPERATURE].temperature = params[0];
+ continue;
+ }
+
+ /* "proximity:<value>" */
+ if (sscanf(buff, "proximity:%g", params+0) == 1) {
+ new_sensors |= SENSORS_PROXIMITY;
+ data->sensors[ID_PROXIMITY].distance = params[0];
+ continue;
+ }
+
+ /* "sync:<time>" is sent after a series of sensor events.
+ * where 'time' is expressed in micro-seconds and corresponds
+ * to the VM time when the real poll occured.
+ */
+ if (sscanf(buff, "sync:%lld", &event_time) == 1) {
+ if (new_sensors) {
+ data->pendingSensors = new_sensors;
+ int64_t t = event_time * 1000LL; /* convert to nano-seconds */
+
+ /* use the time at the first sync: as the base for later
+ * time values */
+ if (data->timeStart == 0) {
+ data->timeStart = data__now_ns();
+ data->timeOffset = data->timeStart - t;
+ }
+ t += data->timeOffset;
+
+ while (new_sensors) {
+ uint32_t i = 31 - __builtin_clz(new_sensors);
+ new_sensors &= ~(1<<i);
+ data->sensors[i].timestamp = t;
+ }
+ return pick_sensor(data, values);
+ } else {
+ D("huh ? sync without any sensor data ?");
+ }
+ continue;
+ }
+ D("huh ? unsupported command");
+ }
+ return -1;
+}
+
+static int
+data__close(struct hw_device_t *dev)
+{
+ SensorPoll* data = (SensorPoll*)dev;
+ if (data) {
+ if (data->events_fd >= 0) {
+ //LOGD("(device close) about to close fd=%d", data->events_fd);
+ close(data->events_fd);
+ }
+ free(data);
+ }
+ return 0;
+}
+
+/** SENSORS POLL DEVICE FUNCTIONS **/
+
+static int poll__close(struct hw_device_t* dev)
+{
+ SensorPoll* ctl = (void*)dev;
+ close(ctl->fd);
+ if (ctl->fd >= 0) {
+ close(ctl->fd);
+ }
+ if (ctl->events_fd >= 0) {
+ close(ctl->events_fd);
+ }
+ free(ctl);
+ return 0;
+}
+
+static int poll__poll(struct sensors_poll_device_t *dev,
+ sensors_event_t* data, int count)
+{
+ SensorPoll* datadev = (void*)dev;
+ int ret;
+ int i;
+ D("%s: dev=%p data=%p count=%d ", __FUNCTION__, dev, data, count);
+
+ for (i = 0; i < count; i++) {
+ ret = data__poll(dev, data);
+ data++;
+ if (ret > MAX_NUM_SENSORS || ret < 0) {
+ return i;
+ }
+ if (!datadev->pendingSensors) {
+ return i + 1;
+ }
+ }
+ return count;
+}
+
+static int poll__activate(struct sensors_poll_device_t *dev,
+ int handle, int enabled)
+{
+ int ret;
+ native_handle_t* hdl;
+ SensorPoll* ctl = (void*)dev;
+ D("%s: dev=%p handle=%x enable=%d ", __FUNCTION__, dev, handle, enabled);
+ if (ctl->fd < 0) {
+ D("%s: OPEN CTRL and DATA ", __FUNCTION__);
+ hdl = control__open_data_source(dev);
+ ret = data__data_open(dev,hdl);
+ }
+ ret = control__activate(dev, handle, enabled);
+ return ret;
+}
+
+static int poll__setDelay(struct sensors_poll_device_t *dev,
+ int handle, int64_t ns)
+{
+ // TODO
+ return 0;
+}
+
+/** MODULE REGISTRATION SUPPORT
+ **
+ ** This is required so that hardware/libhardware/hardware.c
+ ** will dlopen() this library appropriately.
+ **/
+
+/*
+ * the following is the list of all supported sensors.
+ * this table is used to build sSensorList declared below
+ * according to which hardware sensors are reported as
+ * available from the emulator (see get_sensors_list below)
+ *
+ * note: numerical values for maxRange/resolution/power were
+ * taken from the reference AK8976A implementation
+ */
+static const struct sensor_t sSensorListInit[] = {
+ { .name = "Goldfish 3-axis Accelerometer",
+ .vendor = "The Android Open Source Project",
+ .version = 1,
+ .handle = ID_ACCELERATION,
+ .type = SENSOR_TYPE_ACCELEROMETER,
+ .maxRange = 2.8f,
+ .resolution = 1.0f/4032.0f,
+ .power = 3.0f,
+ .reserved = {}
+ },
+
+ { .name = "Goldfish 3-axis Magnetic field sensor",
+ .vendor = "The Android Open Source Project",
+ .version = 1,
+ .handle = ID_MAGNETIC_FIELD,
+ .type = SENSOR_TYPE_MAGNETIC_FIELD,
+ .maxRange = 2000.0f,
+ .resolution = 1.0f,
+ .power = 6.7f,
+ .reserved = {}
+ },
+
+ { .name = "Goldfish Orientation sensor",
+ .vendor = "The Android Open Source Project",
+ .version = 1,
+ .handle = ID_ORIENTATION,
+ .type = SENSOR_TYPE_ORIENTATION,
+ .maxRange = 360.0f,
+ .resolution = 1.0f,
+ .power = 9.7f,
+ .reserved = {}
+ },
+
+ { .name = "Goldfish Temperature sensor",
+ .vendor = "The Android Open Source Project",
+ .version = 1,
+ .handle = ID_TEMPERATURE,
+ .type = SENSOR_TYPE_TEMPERATURE,
+ .maxRange = 80.0f,
+ .resolution = 1.0f,
+ .power = 0.0f,
+ .reserved = {}
+ },
+
+ { .name = "Goldfish Proximity sensor",
+ .vendor = "The Android Open Source Project",
+ .version = 1,
+ .handle = ID_PROXIMITY,
+ .type = SENSOR_TYPE_PROXIMITY,
+ .maxRange = 1.0f,
+ .resolution = 1.0f,
+ .power = 20.0f,
+ .reserved = {}
+ },
+};
+
+static struct sensor_t sSensorList[MAX_NUM_SENSORS];
+
+static int sensors__get_sensors_list(struct sensors_module_t* module,
+ struct sensor_t const** list)
+{
+ int fd = qemud_channel_open(SENSORS_SERVICE_NAME);
+ char buffer[12];
+ int mask, nn, count;
+
+ int ret;
+ if (fd < 0) {
+ E("%s: no qemud connection", __FUNCTION__);
+ return 0;
+ }
+ ret = qemud_channel_send(fd, "list-sensors", -1);
+ if (ret < 0) {
+ E("%s: could not query sensor list: %s", __FUNCTION__,
+ strerror(errno));
+ close(fd);
+ return 0;
+ }
+ ret = qemud_channel_recv(fd, buffer, sizeof buffer-1);
+ if (ret < 0) {
+ E("%s: could not receive sensor list: %s", __FUNCTION__,
+ strerror(errno));
+ close(fd);
+ return 0;
+ }
+ buffer[ret] = 0;
+ close(fd);
+
+ /* the result is a integer used as a mask for available sensors */
+ mask = atoi(buffer);
+ count = 0;
+ for (nn = 0; nn < MAX_NUM_SENSORS; nn++) {
+ if (((1 << nn) & mask) == 0)
+ continue;
+
+ sSensorList[count++] = sSensorListInit[nn];
+ }
+ D("%s: returned %d sensors (mask=%d)", __FUNCTION__, count, mask);
+ *list = sSensorList;
+ return count;
+}
+
+
+static int
+open_sensors(const struct hw_module_t* module,
+ const char* name,
+ struct hw_device_t* *device)
+{
+ int status = -EINVAL;
+
+ D("%s: name=%s", __FUNCTION__, name);
+
+ if (!strcmp(name, SENSORS_HARDWARE_POLL)) {
+ SensorPoll *dev = malloc(sizeof(*dev));
+
+ memset(dev, 0, sizeof(*dev));
+
+ dev->device.common.tag = HARDWARE_DEVICE_TAG;
+ dev->device.common.version = 0;
+ dev->device.common.module = (struct hw_module_t*) module;
+ dev->device.common.close = poll__close;
+ dev->device.poll = poll__poll;
+ dev->device.activate = poll__activate;
+ dev->device.setDelay = poll__setDelay;
+ dev->events_fd = -1;
+ dev->fd = -1;
+
+ *device = &dev->device.common;
+ status = 0;
+ }
+ return status;
+}
+
+
+static struct hw_module_methods_t sensors_module_methods = {
+ .open = open_sensors
+};
+
+const struct sensors_module_t HAL_MODULE_INFO_SYM = {
+ .common = {
+ .tag = HARDWARE_MODULE_TAG,
+ .version_major = 1,
+ .version_minor = 0,
+ .id = SENSORS_HARDWARE_MODULE_ID,
+ .name = "Goldfish SENSORS Module",
+ .author = "The Android Open Source Project",
+ .methods = &sensors_module_methods,
+ },
+ .get_sensors_list = sensors__get_sensors_list
+};
diff --git a/emulator/tests/Android.mk b/emulator/tests/Android.mk
new file mode 100644
index 0000000..04917f4
--- /dev/null
+++ b/emulator/tests/Android.mk
@@ -0,0 +1,17 @@
+# This directory contains various host tests to be used with the emulator
+# NOTE: Most of these are only built and run on Linux.
+
+LOCAL_PATH := $(call my-dir)
+
+# The test-qemud-pipes program is used to check the execution of QEMUD Pipes
+# See external/qemu/docs/ANDROID-QEMUD-PIPES.TXT for details.
+#
+ifeq ($(HOST_OS),XXXXlinux)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := test-qemud-pipes
+LOCAL_SRC_FILES := test-qemud-pipes.c
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # HOST_OS == linux \ No newline at end of file
diff --git a/emulator/tests/test-qemud-pipes.c b/emulator/tests/test-qemud-pipes.c
new file mode 100644
index 0000000..f5db531
--- /dev/null
+++ b/emulator/tests/test-qemud-pipes.c
@@ -0,0 +1,113 @@
+/* This program is used to test the QEMUD fast pipes.
+ * See external/qemu/docs/ANDROID-QEMUD-PIPES.TXT for details.
+ *
+ * The program acts as a simple TCP server that accepts data and sends
+ * them back to the client.
+ */
+
+#include <sys/socket.h>
+#include <net/inet.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#define DEFAULT_PORT 8012
+
+static void
+socket_close(int sock)
+{
+ int old_errno = errno;
+ close(sock);
+ errno = old_errno;
+}
+
+static int
+socket_loopback_server( int port, int type )
+{
+ struct sockaddr_in addr;
+
+ int sock = socket(AF_INET, type, 0);
+ if (sock < 0) {
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ int n = 1;
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
+
+ if (TEMP_FAILURE_RETRY(bind(sock, &addr, sizeof(addr))) < 0) {
+ socket_close(sock);
+ return -1;
+ }
+
+ if (type == SOCK_STREAM) {
+ if (TEMP_FAILURE_RETRY(listen(sock, 4)) < 0) {
+ socket_close(sock);
+ return -1;
+ }
+ }
+
+ return sock;
+}
+
+int main(void)
+{
+ int sock, client;
+ int port = DEFAULT_PORT;
+
+ printf("Starting pipe test server on local port %d\n", port);
+ sock = socket_loopback_server( port, SOCK_STREAM );
+ if (sock < 0) {
+ fprintf(stderr, "Could not start server: %s\n", strerror(errno));
+ return 1;
+ }
+
+ client = accept(sock, NULL, NULL);
+ if (client < 0) {
+ fprintf(stderr, "Server error: %s\n", strerror(errno));
+ return 2;
+ }
+ printf("Client connected!\n");
+
+ /* Now, accept any incoming data, and send it back */
+ for (;;) {
+ char buff[1024], *p;
+ int ret, count;
+
+ do {
+ ret = read(client, buff, sizeof(buff));
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0) {
+ fprintf(stderr, "Client read error: %s\n", strerror(errno));
+ close(client);
+ return 3;
+ }
+ count = ret;
+ p = buff;
+ printf(" received: %d bytes\n", count);
+
+ while (count > 0) {
+ do {
+ ret = write(client, p, count);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0) {
+ fprintf(stderr, "Client write error: %s\n", strerror(errno));
+ close(client);
+ return 4;
+ }
+ printf(" sent: %d bytes\n", ret);
+
+ p += ret;
+ count -= ret;
+ }
+ }
+
+ return 0;
+}
diff --git a/emulator/tools/Android.mk b/emulator/tools/Android.mk
new file mode 100644
index 0000000..1bdbf68
--- /dev/null
+++ b/emulator/tools/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2009 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.
+
+# this file is used to build emulator-specific program tools
+# that should only run in the emulator.
+#
+
+# We're moving the emulator-specific platform libs to
+# development.git/tools/emulator/. The following test is to ensure
+# smooth builds even if the tree contains both versions.
+#
+ifndef BUILD_EMULATOR_QEMU_PROPS
+BUILD_EMULATOR_QEMU_PROPS := true
+
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TARGET_PRODUCT),sim)
+
+# The 'qemu-props' program is run from /system/etc/init.goldfish.rc
+# to setup various system properties sent by the emulator program.
+#
+include $(CLEAR_VARS)
+LOCAL_MODULE := qemu-props
+LOCAL_SRC_FILES := qemu-props.c
+LOCAL_SHARED_LIBRARIES := libcutils
+# we don't want this in 'user' builds which don't have
+# emulator-specific binaries.
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_EXECUTABLE)
+
+endif # TARGET_PRODUCT != sim
+
+endif # BUILD_EMULATOR_QEMU_PROPS
diff --git a/emulator/tools/qemu-props.c b/emulator/tools/qemu-props.c
new file mode 100644
index 0000000..3f086a1
--- /dev/null
+++ b/emulator/tools/qemu-props.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+/* this program is used to read a set of system properties and their values
+ * from the emulator program and set them in the currently-running emulated
+ * system. It does so by connecting to the 'boot-properties' qemud service.
+ *
+ * This program should be run as root and called from
+ * /system/etc/init.goldfish.rc exclusively.
+ */
+
+#define LOG_TAG "qemu-props"
+
+#define DEBUG 1
+
+#if DEBUG
+# include <cutils/log.h>
+# define DD(...) LOGI(__VA_ARGS__)
+#else
+# define DD(...) ((void)0)
+#endif
+
+#include <cutils/properties.h>
+#include <unistd.h>
+#include <hardware/qemud.h>
+
+/* Name of the qemud service we want to connect to.
+ */
+#define QEMUD_SERVICE "boot-properties"
+
+#define MAX_TRIES 5
+
+int main(void)
+{
+ int qemud_fd, count = 0;
+
+ /* try to connect to the qemud service */
+ {
+ int tries = MAX_TRIES;
+
+ while (1) {
+ qemud_fd = qemud_channel_open( "boot-properties" );
+ if (qemud_fd >= 0)
+ break;
+
+ if (--tries <= 0) {
+ DD("Could not connect after too many tries. Aborting");
+ return 1;
+ }
+
+ DD("waiting 1s to wait for qemud.");
+ sleep(1);
+ }
+ }
+
+ DD("connected to '%s' qemud service.", QEMUD_SERVICE);
+
+ /* send the 'list' command to the service */
+ if (qemud_channel_send(qemud_fd, "list", -1) < 0) {
+ DD("could not send command to '%s' service", QEMUD_SERVICE);
+ return 1;
+ }
+
+ /* read each system property as a single line from the service,
+ * until exhaustion.
+ */
+ for (;;)
+ {
+#define BUFF_SIZE (PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX + 2)
+ DD("receiving..");
+ char* q;
+ char temp[BUFF_SIZE];
+ int len = qemud_channel_recv(qemud_fd, temp, sizeof temp - 1);
+
+ /* lone NUL-byte signals end of properties */
+ if (len < 0 || len > BUFF_SIZE-1 || temp[0] == '\0')
+ break;
+
+ temp[len] = '\0'; /* zero-terminate string */
+
+ DD("received: %.*s", len, temp);
+
+ /* separate propery name from value */
+ q = strchr(temp, '=');
+ if (q == NULL) {
+ DD("invalid format, ignored.");
+ continue;
+ }
+ *q++ = '\0';
+
+ if (property_set(temp, q) < 0) {
+ DD("could not set property '%s' to '%s'", temp, q);
+ } else {
+ count += 1;
+ }
+ }
+
+
+ /* finally, close the channel and exit */
+ close(qemud_fd);
+ DD("exiting (%d properties set).", count);
+ return 0;
+}
diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java b/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java
index f04b266..c044353 100644
--- a/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java
+++ b/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java
@@ -134,4 +134,17 @@ public abstract class Bridge {
public Result getViewIndex(Object viewObject) {
return NOT_IMPLEMENTED.createResult();
}
+
+ /**
+ * Utility method returning the baseline value for a given view object. This basically returns
+ * View.getBaseline().
+ *
+ * @param viewObject the object for which to return the index.
+ *
+ * @return the baseline value or -1 if not applicable to the view object or if this layout
+ * library does not implement this method.
+ */
+ public int getViewBaseline(Object viewObject) {
+ return -1;
+ }
}
diff --git a/monkeyrunner/etc/monkeyrunner b/monkeyrunner/etc/monkeyrunner
index 364be2a..fe9be0c 100755
--- a/monkeyrunner/etc/monkeyrunner
+++ b/monkeyrunner/etc/monkeyrunner
@@ -69,6 +69,29 @@ else
jarpath="$frameworkdir/$jarfile"
fi
+# Figure out the path to the swt.jar for the current architecture.
+# if ANDROID_SWT is defined, then just use this.
+# else, if running in the Android source tree, then look for the correct swt folder in prebuilt
+# else, look for the correct swt folder in the SDK under tools/lib/
+swtpath=""
+if [ -n "$ANDROID_SWT" ]; then
+ swtpath="$ANDROID_SWT"
+else
+ vmarch=`java -jar "${frameworkdir}"/archquery.jar`
+ if [ -n "$ANDROID_BUILD_TOP" ]; then
+ osname=`uname -s | tr A-Z a-z`
+ swtpath="${ANDROID_BUILD_TOP}/prebuilt/${osname}-${vmarch}/swt"
+ else
+ swtpath="${frameworkdir}/${vmarch}"
+ fi
+fi
+
+if [ ! -d "$swtpath" ]; then
+ echo "SWT folder '${swtpath}' does not exist."
+ echo "Please export ANDROID_SWT to point to the folder containing swt.jar for your platform."
+ exit 1
+fi
+
# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
# might need more memory, e.g. -Xmx128M
-exec java -Xmx128M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir" -Djava.library.path="$libdir" -Dcom.android.monkeyrunner.bindir="$progdir" -jar "$jarpath" "$@"
+exec java -Xmx128M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir:$swtpath" -Djava.library.path="$libdir" -Dcom.android.monkeyrunner.bindir="$progdir" -jar "$jarpath" "$@"
diff --git a/monkeyrunner/etc/monkeyrunner.bat b/monkeyrunner/etc/monkeyrunner.bat
index 1cf38ca..5028b3f 100644
--- a/monkeyrunner/etc/monkeyrunner.bat
+++ b/monkeyrunner/etc/monkeyrunner.bat
@@ -43,4 +43,21 @@ if exist %frameworkdir%%jarfile% goto JarFileOk
set jarpath=%frameworkdir%%jarfile%
-call %java_exe% -Xmx512m -Djava.ext.dirs=%frameworkdir% -Dcom.android.monkeyrunner.bindir=..\framework -jar %jarpath% %*
+if not defined ANDROID_SWT goto QueryArch
+ set swt_path=%ANDROID_SWT%
+ goto SwtDone
+
+:QueryArch
+
+ for /f %%a in ('%java_exe% -jar %frameworkdir%archquery.jar') do set swt_path=%frameworkdir%%%a
+
+:SwtDone
+
+if exist %swt_path% goto SetPath
+ echo SWT folder '%swt_path%' does not exist.
+ echo Please set ANDROID_SWT to point to the folder containing swt.jar for your platform.
+ exit /B
+
+:SetPath
+
+call %java_exe% -Xmx512m -Djava.ext.dirs=%frameworkdir%;%swt_path% -Dcom.android.monkeyrunner.bindir=..\framework -jar %jarpath% %*
diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
index 37b1cda..649e33c 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
@@ -20,8 +20,6 @@ import java.util.Collections;
import java.util.Map;
import java.util.Set;
-import javax.annotation.Nullable;
-
import org.python.core.ArgParser;
import org.python.core.ClassDictInit;
import org.python.core.Py;
@@ -30,6 +28,8 @@ import org.python.core.PyException;
import org.python.core.PyObject;
import org.python.core.PyTuple;
+import com.android.monkeyrunner.core.IMonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyImage;
import com.android.monkeyrunner.doc.MonkeyRunnerExported;
import com.android.monkeyrunner.easy.HierarchyViewer;
@@ -45,7 +45,7 @@ import com.google.common.collect.ImmutableMap;
* implementation of this class.
*/
@MonkeyRunnerExported(doc = "Represents a device attached to the system.")
-public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
+public class MonkeyDevice extends PyObject implements ClassDictInit {
public static void classDictInit(PyObject dict) {
JythonUtils.convertDocAnnotationsForClass(MonkeyDevice.class, dict);
}
@@ -57,49 +57,41 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
@MonkeyRunnerExported(doc = "Sends a DOWN event, immediately followed by an UP event when used with touch() or press()")
public static final String DOWN_AND_UP = "downAndUp";
+ // TODO: This may not be accessible from jython; if so, remove it.
public enum TouchPressType {
DOWN, UP, DOWN_AND_UP,
}
- public static final Map<String, TouchPressType> TOUCH_NAME_TO_ENUM =
- ImmutableMap.of(MonkeyDevice.DOWN, TouchPressType.DOWN,
- MonkeyDevice.UP, TouchPressType.UP,
- MonkeyDevice.DOWN_AND_UP, TouchPressType.DOWN_AND_UP);
+ public static final Map<String, IMonkeyDevice.TouchPressType> TOUCH_NAME_TO_ENUM =
+ ImmutableMap.of(DOWN, IMonkeyDevice.TouchPressType.DOWN,
+ UP, IMonkeyDevice.TouchPressType.UP,
+ DOWN_AND_UP, IMonkeyDevice.TouchPressType.DOWN_AND_UP);
private static final Set<String> VALID_DOWN_UP_TYPES = TOUCH_NAME_TO_ENUM.keySet();
- /**
- * Create a MonkeyMananger for talking to this device.
- *
- * NOTE: This is not part of the jython API.
- *
- * @return the MonkeyManager
- */
- public abstract MonkeyManager getManager();
-
- /**
- * Dispose of any native resoureces this device may have taken hold of.
- *
- * NOTE: This is not part of the jython API.
- */
- public abstract void dispose();
-
- /**
- * @return hierarchy viewer implementation for querying state of the view
- * hierarchy.
- */
- public abstract HierarchyViewer getHierarchyViewer();
+ private IMonkeyDevice impl;
+
+ public MonkeyDevice(IMonkeyDevice impl) {
+ this.impl = impl;
+ }
+
+ public IMonkeyDevice getImpl() {
+ return impl;
+ }
@MonkeyRunnerExported(doc = "Get the HierarchyViewer object for the device.",
returns = "A HierarchyViewer object")
public HierarchyViewer getHierarchyViewer(PyObject[] args, String[] kws) {
- return getHierarchyViewer();
+ return impl.getHierarchyViewer();
}
@MonkeyRunnerExported(doc =
"Gets the device's screen buffer, yielding a screen capture of the entire display.",
returns = "A MonkeyImage object (a bitmap wrapper)")
- public abstract MonkeyImage takeSnapshot();
+ public MonkeyImage takeSnapshot() {
+ IMonkeyImage image = impl.takeSnapshot();
+ return new MonkeyImage(image);
+ }
@MonkeyRunnerExported(doc = "Given the name of a variable on the device, " +
"returns the variable's value",
@@ -111,7 +103,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
ArgParser ap = JythonUtils.createArgParser(args, kws);
Preconditions.checkNotNull(ap);
- return getProperty(ap.getString(0));
+ return impl.getProperty(ap.getString(0));
}
@MonkeyRunnerExported(doc = "Synonym for getProperty()",
@@ -121,7 +113,8 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
public String getSystemProperty(PyObject[] args, String[] kws) {
ArgParser ap = JythonUtils.createArgParser(args, kws);
Preconditions.checkNotNull(ap);
- return getSystemProperty(ap.getString(0));
+
+ return impl.getSystemProperty(ap.getString(0));
}
@MonkeyRunnerExported(doc = "Sends a touch event at the specified location",
@@ -150,7 +143,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
// bad stuff was passed in, just use the already specified default value
type = MonkeyDevice.DOWN_AND_UP;
}
- touch(x, y, TOUCH_NAME_TO_ENUM.get(type));
+ impl.touch(x, y, TOUCH_NAME_TO_ENUM.get(type));
}
@MonkeyRunnerExported(doc = "Simulates dragging (touch, hold, and move) on the device screen.",
@@ -185,7 +178,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
int steps = ap.getInt(3, 10);
- drag(startx, starty, endx, endy, steps, ms);
+ impl.drag(startx, starty, endx, endy, steps, ms);
}
@MonkeyRunnerExported(doc = "Send a key event to the specified key",
@@ -213,7 +206,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
// bad stuff was passed in, just use the already specified default value
type = MonkeyDevice.DOWN_AND_UP;
}
- press(name, TOUCH_NAME_TO_ENUM.get(type));
+ impl.press(name, TOUCH_NAME_TO_ENUM.get(type));
}
@MonkeyRunnerExported(doc = "Types the specified string on the keyboard. This is " +
@@ -225,7 +218,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
String message = ap.getString(0);
- type(message);
+ impl.type(message);
}
@MonkeyRunnerExported(doc = "Executes an adb shell command and returns the result, if any.",
@@ -237,7 +230,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
String cmd = ap.getString(0);
- return shell(cmd);
+ return impl.shell(cmd);
}
@MonkeyRunnerExported(doc = "Reboots the specified device into a specified bootloader.",
@@ -249,7 +242,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
String into = ap.getString(0, null);
- reboot(into);
+ impl.reboot(into);
}
@MonkeyRunnerExported(doc = "Installs the specified Android package (.apk file) " +
@@ -262,7 +255,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
String path = ap.getString(0);
- return installPackage(path);
+ return impl.installPackage(path);
}
@MonkeyRunnerExported(doc = "Deletes the specified package from the device, including its " +
@@ -275,7 +268,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
String packageName = ap.getString(0);
- return removePackage(packageName);
+ return impl.removePackage(packageName);
}
@MonkeyRunnerExported(doc = "Starts an Activity on the device by sending an Intent " +
@@ -308,7 +301,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
String component = ap.getString(6, null);
int flags = ap.getInt(7, 0);
- startActivity(uri, action, data, mimetype, categories, extras, component, flags);
+ impl.startActivity(uri, action, data, mimetype, categories, extras, component, flags);
}
@MonkeyRunnerExported(doc = "Sends a broadcast intent to the device.",
@@ -340,7 +333,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
String component = ap.getString(6, null);
int flags = ap.getInt(7, 0);
- broadcastIntent(uri, action, data, mimetype, categories, extras, component, flags);
+ impl.broadcastIntent(uri, action, data, mimetype, categories, extras, component, flags);
}
@MonkeyRunnerExported(doc = "Run the specified package with instrumentation and return " +
@@ -368,7 +361,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
instrumentArgs = Collections.emptyMap();
}
- Map<String, Object> result = instrument(packageName, instrumentArgs);
+ Map<String, Object> result = impl.instrument(packageName, instrumentArgs);
return JythonUtils.convertMapToDict(result);
}
@@ -377,34 +370,6 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
ArgParser ap = JythonUtils.createArgParser(args, kws);
Preconditions.checkNotNull(ap);
- wake();
+ impl.wake();
}
-
- /**
- * Reboot the device.
- *
- * @param into which bootloader to boot into. Null means default reboot.
- */
- public abstract void reboot(@Nullable String into);
-
- public abstract String getProperty(String key);
- public abstract String getSystemProperty(String key);
- public abstract void touch(int x, int y, TouchPressType type);
- public abstract void press(String keyName, TouchPressType type);
- public abstract void drag(int startx, int starty, int endx, int endy, int steps, long ms);
- public abstract void type(String string);
- public abstract String shell(String cmd);
- public abstract boolean installPackage(String path);
- public abstract boolean removePackage(String packageName);
- public abstract void startActivity(@Nullable String uri, @Nullable String action,
- @Nullable String data, @Nullable String mimetype,
- Collection<String> categories, Map<String, Object> extras, @Nullable String component,
- int flags);
- public abstract void broadcastIntent(@Nullable String uri, @Nullable String action,
- @Nullable String data, @Nullable String mimetype,
- Collection<String> categories, Map<String, Object> extras, @Nullable String component,
- int flags);
- public abstract Map<String, Object> instrument(String packageName,
- Map<String, Object> args);
- public abstract void wake();
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
index 70201ee..b55b4f3 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
@@ -17,6 +17,7 @@ package com.android.monkeyrunner;
import com.google.common.base.Preconditions;
+import com.android.monkeyrunner.core.IMonkeyImage;
import com.android.monkeyrunner.doc.MonkeyRunnerExported;
import org.python.core.ArgParser;
@@ -25,61 +26,31 @@ import org.python.core.PyInteger;
import org.python.core.PyObject;
import org.python.core.PyTuple;
-import java.awt.Graphics;
import java.awt.image.BufferedImage;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.util.Iterator;
-import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.imageio.ImageIO;
-import javax.imageio.ImageWriter;
-import javax.imageio.stream.ImageOutputStream;
-
/**
* Jython object to encapsulate images that have been taken.
*/
@MonkeyRunnerExported(doc = "An image")
-public abstract class MonkeyImage extends PyObject implements ClassDictInit {
+public class MonkeyImage extends PyObject implements ClassDictInit {
private static Logger LOG = Logger.getLogger(MonkeyImage.class.getCanonicalName());
public static void classDictInit(PyObject dict) {
JythonUtils.convertDocAnnotationsForClass(MonkeyImage.class, dict);
}
- /**
- * Convert the MonkeyImage into a BufferedImage.
- *
- * @return a BufferedImage for this MonkeyImage.
- */
- public abstract BufferedImage createBufferedImage();
-
- // Cache the BufferedImage so we don't have to generate it every time.
- private WeakReference<BufferedImage> cachedBufferedImage = null;
+ private IMonkeyImage impl;
- /**
- * Utility method to handle getting the BufferedImage and managing the cache.
- *
- * @return the BufferedImage for this image.
- */
- private BufferedImage getBufferedImage() {
- // Check the cache first
- if (cachedBufferedImage != null) {
- BufferedImage img = cachedBufferedImage.get();
- if (img != null) {
- return img;
- }
- }
+ public MonkeyImage(IMonkeyImage impl) {
+ this.impl = impl;
+ }
- // Not in the cache, so create it and cache it.
- BufferedImage img = createBufferedImage();
- cachedBufferedImage = new WeakReference<BufferedImage>(img);
- return img;
+ public IMonkeyImage getImpl() {
+ return impl;
}
+
@MonkeyRunnerExported(doc = "Converts the MonkeyImage into a particular format and returns " +
"the result as a String. Use this to get access to the raw" +
"pixels in a particular format. String output is for better " +
@@ -93,16 +64,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
String format = ap.getString(0, "png");
-
- BufferedImage argb = convertSnapshot();
-
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- try {
- ImageIO.write(argb, format, os);
- } catch (IOException e) {
- return new byte[0];
- }
- return os.toByteArray();
+ return impl.convertToBytes(format);
}
@MonkeyRunnerExported(doc = "Write the MonkeyImage to a file. If no " +
@@ -120,38 +82,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit {
String path = ap.getString(0);
String format = ap.getString(1, null);
-
- if (format != null) {
- return writeToFile(path, format);
- }
- int offset = path.lastIndexOf('.');
- if (offset < 0) {
- return writeToFile(path, "png");
- }
- String ext = path.substring(offset + 1);
- Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix(ext);
- if (!writers.hasNext()) {
- return writeToFile(path, "png");
- }
- ImageWriter writer = writers.next();
- BufferedImage image = convertSnapshot();
- try {
- File f = new File(path);
- f.delete();
-
- ImageOutputStream outputStream = ImageIO.createImageOutputStream(f);
- writer.setOutput(outputStream);
-
- try {
- writer.write(image);
- } finally {
- writer.dispose();
- outputStream.flush();
- }
- } catch (IOException e) {
- return false;
- }
- return true;
+ return impl.writeToFile(path, format);
}
@MonkeyRunnerExported(doc = "Get a single ARGB (alpha, red, green, blue) pixel at location " +
@@ -167,7 +98,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit {
int x = ap.getInt(0);
int y = ap.getInt(1);
- int pixel = getPixel(x, y);
+ int pixel = impl.getPixel(x, y);
PyInteger a = new PyInteger((pixel & 0xFF000000) >> 24);
PyInteger r = new PyInteger((pixel & 0x00FF0000) >> 16);
PyInteger g = new PyInteger((pixel & 0x0000FF00) >> 8);
@@ -188,35 +119,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit {
int x = ap.getInt(0);
int y = ap.getInt(1);
- return getPixel(x, y);
- }
-
- private int getPixel(int x, int y) {
- BufferedImage image = getBufferedImage();
- return image.getRGB(x, y);
- }
-
- private BufferedImage convertSnapshot() {
- BufferedImage image = getBufferedImage();
-
- // Convert the image to ARGB so ImageIO writes it out nicely
- BufferedImage argb = new BufferedImage(image.getWidth(), image.getHeight(),
- BufferedImage.TYPE_INT_ARGB);
- Graphics g = argb.createGraphics();
- g.drawImage(image, 0, 0, null);
- g.dispose();
- return argb;
- }
-
- public boolean writeToFile(String path, String format) {
- BufferedImage argb = convertSnapshot();
-
- try {
- ImageIO.write(argb, format, new File(path));
- } catch (IOException e) {
- return false;
- }
- return true;
+ return impl.getPixel(x, y);
}
@MonkeyRunnerExported(doc = "Compare this MonkeyImage object to aother MonkeyImage object.",
@@ -231,72 +134,13 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
PyObject otherObject = ap.getPyObject(0);
- MonkeyImage other = (MonkeyImage) otherObject.__tojava__(MonkeyImage.class);
+ // TODO: check if this conversion wortks
+ IMonkeyImage other = (IMonkeyImage) otherObject.__tojava__(
+ IMonkeyImage.class);
double percent = JythonUtils.getFloat(ap, 1, 1.0);
- BufferedImage otherImage = other.getBufferedImage();
- BufferedImage myImage = getBufferedImage();
-
- // Easy size check
- if (otherImage.getWidth() != myImage.getWidth()) {
- return false;
- }
- if (otherImage.getHeight() != myImage.getHeight()) {
- return false;
- }
-
- int[] otherPixel = new int[1];
- int[] myPixel = new int[1];
-
- int width = myImage.getWidth();
- int height = myImage.getHeight();
-
- int numDiffPixels = 0;
- // Now, go through pixel-by-pixel and check that the images are the same;
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
- numDiffPixels++;
- }
- }
- }
- double numberPixels = (height * width);
- double diffPercent = numDiffPixels / numberPixels;
- return percent <= 1.0 - diffPercent;
- }
-
- private static class BufferedImageMonkeyImage extends MonkeyImage {
- private final BufferedImage image;
-
- public BufferedImageMonkeyImage(BufferedImage image) {
- this.image = image;
- }
-
- @Override
- public BufferedImage createBufferedImage() {
- return image;
- }
- }
-
- /* package */ static MonkeyImage loadImageFromFile(String path) {
- File f = new File(path);
- if (f.exists() && f.canRead()) {
- try {
- BufferedImage bufferedImage = ImageIO.read(new File(path));
- if (bufferedImage == null) {
- LOG.log(Level.WARNING, "Cannot decode file %s", path);
- return null;
- }
- return new BufferedImageMonkeyImage(bufferedImage);
- } catch (IOException e) {
- LOG.log(Level.WARNING, "Exception trying to decode image", e);
- return null;
- }
- } else {
- LOG.log(Level.WARNING, "Cannot read file %s", path);
- return null;
- }
+ return impl.sameAs(other, percent);
}
@MonkeyRunnerExported(doc = "Copy a rectangular region of the image.",
@@ -315,7 +159,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit {
int w = rect.__getitem__(2).asInt();
int h = rect.__getitem__(3).asInt();
- BufferedImage image = getBufferedImage();
- return new BufferedImageMonkeyImage(image.getSubimage(x, y, w, h));
+ IMonkeyImage image = impl.getSubImage(x, y, w, h);
+ return new MonkeyImage(image);
}
} \ No newline at end of file
diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
index 5f137cd..5529802 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
@@ -19,6 +19,10 @@ import com.google.common.base.Functions;
import com.google.common.base.Preconditions;
import com.google.common.collect.Collections2;
+import com.android.monkeyrunner.core.IMonkeyBackend;
+import com.android.monkeyrunner.core.IMonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyImage;
+import com.android.monkeyrunner.core.MonkeyImageBase;
import com.android.monkeyrunner.doc.MonkeyRunnerExported;
import org.python.core.ArgParser;
@@ -38,7 +42,7 @@ import javax.swing.JOptionPane;
@MonkeyRunnerExported(doc = "Main entry point for MonkeyRunner")
public class MonkeyRunner extends PyObject implements ClassDictInit {
private static final Logger LOG = Logger.getLogger(MonkeyRunner.class.getCanonicalName());
- private static MonkeyRunnerBackend backend;
+ private static IMonkeyBackend backend;
public static void classDictInit(PyObject dict) {
JythonUtils.convertDocAnnotationsForClass(MonkeyRunner.class, dict);
@@ -49,7 +53,7 @@ public class MonkeyRunner extends PyObject implements ClassDictInit {
*
* @param backend the backend to use.
*/
- /* package */ static void setBackend(MonkeyRunnerBackend backend) {
+ /* package */ static void setBackend(IMonkeyBackend backend) {
MonkeyRunner.backend = backend;
}
@@ -71,8 +75,10 @@ public class MonkeyRunner extends PyObject implements ClassDictInit {
timeoutMs = Long.MAX_VALUE;
}
- return backend.waitForConnection(timeoutMs,
+ IMonkeyDevice device = backend.waitForConnection(timeoutMs,
ap.getString(1, ".*"));
+ MonkeyDevice monkeyDevice = new MonkeyDevice(device);
+ return monkeyDevice;
}
@MonkeyRunnerExported(doc = "Pause the currently running program for the specified " +
@@ -185,8 +191,8 @@ public class MonkeyRunner extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
String path = ap.getString(0);
-
- return MonkeyImage.loadImageFromFile(path);
+ IMonkeyImage image = MonkeyImageBase.loadImageFromFile(path);
+ return new MonkeyImage(image);
}
/**
diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
index 5244dbc..8c9942c 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
@@ -20,6 +20,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.android.monkeyrunner.adb.AdbBackend;
+import com.android.monkeyrunner.core.IMonkeyBackend;
import com.android.monkeyrunner.stub.StubBackend;
import org.python.util.PythonInterpreter;
@@ -50,7 +51,7 @@ public class MonkeyRunnerStarter {
private static final Logger LOG = Logger.getLogger(MonkeyRunnerStarter.class.getName());
private static final String MONKEY_RUNNER_MAIN_MANIFEST_NAME = "MonkeyRunnerStartupRunner";
- private final MonkeyRunnerBackend backend;
+ private final IMonkeyBackend backend;
private final MonkeyRunnerOptions options;
public MonkeyRunnerStarter(MonkeyRunnerOptions options) {
@@ -68,7 +69,7 @@ public class MonkeyRunnerStarter {
* @param backendName the name of the backend to create
* @return the new backend, or null if none were found.
*/
- public static MonkeyRunnerBackend createBackendByName(String backendName) {
+ public static IMonkeyBackend createBackendByName(String backendName) {
if ("adb".equals(backendName)) {
return new AdbBackend();
} else if ("stub".equals(backendName)) {
diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java
index 455d131..49cac08 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java
@@ -19,8 +19,8 @@ import com.google.common.collect.Lists;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.IDevice;
-import com.android.monkeyrunner.MonkeyDevice;
-import com.android.monkeyrunner.MonkeyRunnerBackend;
+import com.android.monkeyrunner.core.IMonkeyBackend;
+import com.android.monkeyrunner.core.IMonkeyDevice;
import com.android.sdklib.SdkConstants;
import java.io.File;
@@ -32,12 +32,11 @@ import java.util.regex.Pattern;
/**
* Backend implementation that works over ADB to talk to the device.
*/
-public class AdbBackend implements MonkeyRunnerBackend {
+public class AdbBackend implements IMonkeyBackend {
private static Logger LOG = Logger.getLogger(AdbBackend.class.getCanonicalName());
// How long to wait each time we check for the device to be connected.
private static final int CONNECTION_ITERATION_TIMEOUT_MS = 200;
- private final List<AdbMonkeyDevice> devices = Lists.newArrayList();
-
+ private final List<IMonkeyDevice> devices = Lists.newArrayList();
private final AndroidDebugBridge bridge;
public AdbBackend() {
@@ -87,18 +86,20 @@ public class AdbBackend implements MonkeyRunnerBackend {
return null;
}
- public MonkeyDevice waitForConnection() {
+ @Override
+ public IMonkeyDevice waitForConnection() {
return waitForConnection(Integer.MAX_VALUE, ".*");
}
- public MonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex) {
+ @Override
+ public IMonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex) {
do {
IDevice device = findAttacedDevice(deviceIdRegex);
// Only return the device when it is online
if (device != null && device.getState() == IDevice.DeviceState.ONLINE) {
- AdbMonkeyDevice amd = new AdbMonkeyDevice(device);
- devices.add(amd);
- return amd;
+ IMonkeyDevice monkeyDevice = new AdbMonkeyDevice(device);
+ devices.add(monkeyDevice);
+ return monkeyDevice;
}
try {
@@ -113,8 +114,9 @@ public class AdbBackend implements MonkeyRunnerBackend {
return null;
}
+ @Override
public void shutdown() {
- for (AdbMonkeyDevice device : devices) {
+ for (IMonkeyDevice device : devices) {
device.dispose();
}
AndroidDebugBridge.terminate();
diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java
index 614e656..60eaba9 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java
@@ -25,10 +25,10 @@ import com.android.ddmlib.IDevice;
import com.android.ddmlib.InstallException;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
-import com.android.monkeyrunner.MonkeyDevice;
-import com.android.monkeyrunner.MonkeyImage;
import com.android.monkeyrunner.MonkeyManager;
import com.android.monkeyrunner.adb.LinearInterpolator.Point;
+import com.android.monkeyrunner.core.IMonkeyImage;
+import com.android.monkeyrunner.core.IMonkeyDevice;
import com.android.monkeyrunner.easy.HierarchyViewer;
import java.io.IOException;
@@ -48,7 +48,7 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
-public class AdbMonkeyDevice extends MonkeyDevice {
+public class AdbMonkeyDevice implements IMonkeyDevice {
private static final Logger LOG = Logger.getLogger(AdbMonkeyDevice.class.getName());
private static final String[] ZERO_LENGTH_STRING_ARRAY = new String[0];
@@ -90,6 +90,7 @@ public class AdbMonkeyDevice extends MonkeyDevice {
private void executeAsyncCommand(final String command,
final LoggingOutputReceiver logger) {
executor.submit(new Runnable() {
+ @Override
public void run() {
try {
device.executeShellCommand(command, logger);
@@ -190,7 +191,7 @@ public class AdbMonkeyDevice extends MonkeyDevice {
}
@Override
- public MonkeyImage takeSnapshot() {
+ public IMonkeyImage takeSnapshot() {
try {
return new AdbMonkeyImage(device.getScreenshot());
} catch (TimeoutException e) {
@@ -505,6 +506,7 @@ public class AdbMonkeyDevice extends MonkeyDevice {
LinearInterpolator.Point start = new LinearInterpolator.Point(startx, starty);
LinearInterpolator.Point end = new LinearInterpolator.Point(endx, endy);
lerp.interpolate(start, end, new LinearInterpolator.Callback() {
+ @Override
public void step(Point point) {
try {
manager.touchMove(point.getX(), point.getY());
@@ -519,6 +521,7 @@ public class AdbMonkeyDevice extends MonkeyDevice {
}
}
+ @Override
public void start(Point point) {
try {
manager.touchDown(point.getX(), point.getY());
@@ -534,6 +537,7 @@ public class AdbMonkeyDevice extends MonkeyDevice {
}
}
+ @Override
public void end(Point point) {
try {
manager.touchMove(point.getX(), point.getY());
diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java
index fc32600..e2bd86e 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java
@@ -16,15 +16,15 @@
package com.android.monkeyrunner.adb;
import com.android.ddmlib.RawImage;
-import com.android.monkeyrunner.MonkeyImage;
import com.android.monkeyrunner.adb.image.ImageUtils;
+import com.android.monkeyrunner.core.MonkeyImageBase;
import java.awt.image.BufferedImage;
/**
* ADB implementation of the MonkeyImage class.
*/
-public class AdbMonkeyImage extends MonkeyImage {
+public class AdbMonkeyImage extends MonkeyImageBase {
private final RawImage image;
/**
diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java b/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java
index 7e31ea5..5a317f1 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java
@@ -16,9 +16,11 @@
package com.android.monkeyrunner.adb.image;
import com.android.ddmlib.RawImage;
-import com.android.monkeyrunner.MonkeyDevice;
import com.android.monkeyrunner.adb.AdbBackend;
import com.android.monkeyrunner.adb.AdbMonkeyImage;
+import com.android.monkeyrunner.core.IMonkeyBackend;
+import com.android.monkeyrunner.core.IMonkeyImage;
+import com.android.monkeyrunner.core.IMonkeyDevice;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -94,13 +96,13 @@ public class CaptureRawAndConvertedImage {
}
public static void main(String[] args) throws IOException {
- AdbBackend backend = new AdbBackend();
- MonkeyDevice device = backend.waitForConnection();
- AdbMonkeyImage snapshot = (AdbMonkeyImage) device.takeSnapshot();
+ IMonkeyBackend backend = new AdbBackend();
+ IMonkeyDevice device = backend.waitForConnection();
+ IMonkeyImage snapshot = (IMonkeyImage) device.takeSnapshot();
// write out to a file
snapshot.writeToFile("output.png", "png");
- writeOutImage(snapshot.getRawImage(), "output.raw");
+ writeOutImage(((AdbMonkeyImage)snapshot).getRawImage(), "output.raw");
System.exit(0);
}
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java
index e199a75..ca3195c 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java
@@ -15,8 +15,9 @@
*/
package com.android.monkeyrunner.controller;
-import com.android.monkeyrunner.MonkeyDevice;
import com.android.monkeyrunner.adb.AdbBackend;
+import com.android.monkeyrunner.core.IMonkeyBackend;
+import com.android.monkeyrunner.core.IMonkeyDevice;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
@@ -36,9 +37,10 @@ public class MonkeyController extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
+ @Override
public void run() {
- AdbBackend adb = new AdbBackend();
- final MonkeyDevice device = adb.waitForConnection();
+ IMonkeyBackend adb = new AdbBackend();
+ final IMonkeyDevice device = adb.waitForConnection();
MonkeyControllerFrame mf = new MonkeyControllerFrame(device);
mf.setVisible(true);
mf.addWindowListener(new WindowAdapter() {
diff --git a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java
index 7f5a7d8..7750936 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java
@@ -15,10 +15,10 @@
*/
package com.android.monkeyrunner.controller;
-import com.android.monkeyrunner.MonkeyDevice;
-import com.android.monkeyrunner.MonkeyImage;
import com.android.monkeyrunner.MonkeyManager;
import com.android.monkeyrunner.PhysicalButton;
+import com.android.monkeyrunner.core.IMonkeyImage;
+import com.android.monkeyrunner.core.IMonkeyDevice;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
@@ -61,7 +61,7 @@ public class MonkeyControllerFrame extends JFrame {
}
});
- private final MonkeyDevice device;
+ private final IMonkeyDevice device;
private class PressAction extends AbstractAction {
private final PhysicalButton button;
@@ -85,7 +85,7 @@ public class MonkeyControllerFrame extends JFrame {
return button;
}
- public MonkeyControllerFrame(MonkeyDevice device) {
+ public MonkeyControllerFrame(IMonkeyDevice device) {
super("MonkeyController");
this.device = device;
@@ -155,7 +155,7 @@ public class MonkeyControllerFrame extends JFrame {
}
private void updateScreen() {
- MonkeyImage snapshot = device.takeSnapshot();
+ IMonkeyImage snapshot = device.takeSnapshot();
currentImage = snapshot.createBufferedImage();
imageLabel.setIcon(new ImageIcon(currentImage));
diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyBackend.java
index 216d214..3c1b943 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyBackend.java
@@ -13,13 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.monkeyrunner;
+package com.android.monkeyrunner.core;
+
+import com.android.monkeyrunner.MonkeyDevice;
/**
* Interface between MonkeyRunner common code and the MonkeyRunner backend. The backend is
* responsible for communicating between the host and the device.
*/
-public interface MonkeyRunnerBackend {
+public interface IMonkeyBackend {
+ /**
+ * Wait for a default device to connect to the backend.
+ *
+ * @return the connected device (or null if timeout);
+ */
+ IMonkeyDevice waitForConnection();
+
/**
* Wait for a device to connect to the backend.
*
@@ -27,7 +36,7 @@ public interface MonkeyRunnerBackend {
* @param deviceIdRegex the regular expression to specify which device to wait for.
* @return the connected device (or null if timeout);
*/
- MonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex);
+ IMonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex);
/**
* Shutdown the backend and cleanup any resources it was using.
diff --git a/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyDevice.java
new file mode 100644
index 0000000..c081a56
--- /dev/null
+++ b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyDevice.java
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+package com.android.monkeyrunner.core;
+
+import com.android.monkeyrunner.MonkeyManager;
+import com.android.monkeyrunner.easy.HierarchyViewer;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * MonkeyDevice interface.
+ */
+public interface IMonkeyDevice {
+ enum TouchPressType {
+ DOWN, UP, DOWN_AND_UP,
+ }
+
+ /**
+ * Create a MonkeyMananger for talking to this device.
+ *
+ * @return the MonkeyManager
+ */
+ MonkeyManager getManager();
+
+ /**
+ * Dispose of any native resources this device may have taken hold of.
+ */
+ void dispose();
+
+ /**
+ * @return hierarchy viewer implementation for querying state of the view
+ * hierarchy.
+ */
+ HierarchyViewer getHierarchyViewer();
+
+ /**
+ * Take the current screen's snapshot.
+ * @return the snapshot image
+ */
+ IMonkeyImage takeSnapshot();
+
+ /**
+ * Reboot the device.
+ *
+ * @param into which bootloader to boot into. Null means default reboot.
+ */
+ void reboot(@Nullable String into);
+
+ /**
+ * Get device's property.
+ *
+ * @param key the property name
+ * @return the property value
+ */
+ String getProperty(String key);
+
+ /**
+ * Get system property.
+ *
+ * @param key the name of the system property
+ * @return the property value
+ */
+ String getSystemProperty(String key);
+
+ /**
+ * Perform a touch of the given type at (x,y).
+ *
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param type the touch type
+ */
+ void touch(int x, int y, TouchPressType type);
+
+ /**
+ * Perform a press of a given type using a given key.
+ *
+ * TODO: define standard key names in a separate class or enum
+ *
+ * @param keyName the name of the key to use
+ * @param type the type of press to perform
+ */
+ void press(String keyName, TouchPressType type);
+
+ /**
+ * Perform a drag from one one location to another
+ *
+ * @param startx the x coordinate of the drag's starting point
+ * @param starty the y coordinate of the drag's starting point
+ * @param endx the x coordinate of the drag's end point
+ * @param endy the y coordinate of the drag's end point
+ * @param steps the number of steps to take when interpolating points
+ * @param ms the duration of the drag
+ */
+ void drag(int startx, int starty, int endx, int endy, int steps, long ms);
+
+ /**
+ * Type a given string.
+ *
+ * @param string the string to type
+ */
+ void type(String string);
+
+ /**
+ * Execute a shell command.
+ *
+ * @param cmd the command to execute
+ * @return the output of the command
+ */
+ String shell(String cmd);
+
+ /**
+ * Install a given package.
+ *
+ * @param path the path to the installation package
+ * @return true if success
+ */
+ boolean installPackage(String path);
+
+ /**
+ * Uninstall a given package.
+ *
+ * @param packageName the name of the package
+ * @return true if success
+ */
+ boolean removePackage(String packageName);
+
+ /**
+ * Start an activity.
+ *
+ * @param uri the URI for the Intent
+ * @param action the action for the Intent
+ * @param data the data URI for the Intent
+ * @param mimeType the mime type for the Intent
+ * @param categories the category names for the Intent
+ * @param extras the extras to add to the Intent
+ * @param component the component of the Intent
+ * @param flags the flags for the Intent
+ */
+ void startActivity(@Nullable String uri, @Nullable String action,
+ @Nullable String data, @Nullable String mimeType,
+ Collection<String> categories, Map<String, Object> extras, @Nullable String component,
+ int flags);
+
+ /**
+ * Send a broadcast intent to the device.
+ *
+ * @param uri the URI for the Intent
+ * @param action the action for the Intent
+ * @param data the data URI for the Intent
+ * @param mimeType the mime type for the Intent
+ * @param categories the category names for the Intent
+ * @param extras the extras to add to the Intent
+ * @param component the component of the Intent
+ * @param flags the flags for the Intent
+ */
+ void broadcastIntent(@Nullable String uri, @Nullable String action,
+ @Nullable String data, @Nullable String mimeType,
+ Collection<String> categories, Map<String, Object> extras, @Nullable String component,
+ int flags);
+
+ /**
+ * Run the specified package with instrumentation and return the output it
+ * generates.
+ *
+ * Use this to run a test package using InstrumentationTestRunner.
+ *
+ * @param packageName The class to run with instrumentation. The format is
+ * packageName/className. Use packageName to specify the Android package to
+ * run, and className to specify the class to run within that package. For
+ * test packages, this is usually testPackageName/InstrumentationTestRunner
+ * @param args a map of strings to objects containing the arguments to pass
+ * to this instrumentation.
+ * @return A map of strings to objects for the output from the package.
+ * For a test package, contains a single key-value pair: the key is 'stream'
+ * and the value is a string containing the test output.
+ */
+ Map<String, Object> instrument(String packageName,
+ Map<String, Object> args);
+
+ /**
+ * Wake up the screen on the device.
+ */
+ void wake();
+}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyImage.java b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyImage.java
new file mode 100644
index 0000000..5a24fa7
--- /dev/null
+++ b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyImage.java
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+package com.android.monkeyrunner.core;
+
+import java.awt.image.BufferedImage;
+
+/**
+ * MonkeyImage interface.
+ *
+ * This interface defines an image representing a screen snapshot.
+ */
+public interface IMonkeyImage {
+ // TODO: add java docs
+ BufferedImage createBufferedImage();
+ BufferedImage getBufferedImage();
+
+ IMonkeyImage getSubImage(int x, int y, int w, int h);
+
+ byte[] convertToBytes(String format);
+ boolean writeToFile(String path, String format);
+ int getPixel(int x, int y);
+ boolean sameAs(IMonkeyImage other, double percent);
+}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/core/MonkeyImageBase.java b/monkeyrunner/src/com/android/monkeyrunner/core/MonkeyImageBase.java
new file mode 100644
index 0000000..04ccb93
--- /dev/null
+++ b/monkeyrunner/src/com/android/monkeyrunner/core/MonkeyImageBase.java
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+package com.android.monkeyrunner.core;
+
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
+
+/**
+ * Base class with basic functionality for MonkeyImage implementations.
+ */
+public abstract class MonkeyImageBase implements IMonkeyImage {
+ private static Logger LOG = Logger.getLogger(MonkeyImageBase.class.getCanonicalName());
+
+ /**
+ * Convert the MonkeyImage to a BufferedImage.
+ *
+ * @return a BufferedImage for this MonkeyImage.
+ */
+ @Override
+ public abstract BufferedImage createBufferedImage();
+
+ // Cache the BufferedImage so we don't have to generate it every time.
+ private WeakReference<BufferedImage> cachedBufferedImage = null;
+
+ /**
+ * Utility method to handle getting the BufferedImage and managing the cache.
+ *
+ * @return the BufferedImage for this image.
+ */
+ @Override
+ public BufferedImage getBufferedImage() {
+ // Check the cache first
+ if (cachedBufferedImage != null) {
+ BufferedImage img = cachedBufferedImage.get();
+ if (img != null) {
+ return img;
+ }
+ }
+
+ // Not in the cache, so create it and cache it.
+ BufferedImage img = createBufferedImage();
+ cachedBufferedImage = new WeakReference<BufferedImage>(img);
+ return img;
+ }
+
+ @Override
+ public byte[] convertToBytes(String format) {
+ BufferedImage argb = convertSnapshot();
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ try {
+ ImageIO.write(argb, format, os);
+ } catch (IOException e) {
+ return new byte[0];
+ }
+ return os.toByteArray();
+ }
+
+ @Override
+ public boolean writeToFile(String path, String format) {
+ if (format != null) {
+ return writeToFileHelper(path, format);
+ }
+ int offset = path.lastIndexOf('.');
+ if (offset < 0) {
+ return writeToFileHelper(path, "png");
+ }
+ String ext = path.substring(offset + 1);
+ Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix(ext);
+ if (!writers.hasNext()) {
+ return writeToFileHelper(path, "png");
+ }
+ ImageWriter writer = writers.next();
+ BufferedImage image = convertSnapshot();
+ try {
+ File f = new File(path);
+ f.delete();
+
+ ImageOutputStream outputStream = ImageIO.createImageOutputStream(f);
+ writer.setOutput(outputStream);
+
+ try {
+ writer.write(image);
+ } finally {
+ writer.dispose();
+ outputStream.flush();
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int getPixel(int x, int y) {
+ BufferedImage image = getBufferedImage();
+ return image.getRGB(x, y);
+ }
+
+ private BufferedImage convertSnapshot() {
+ BufferedImage image = getBufferedImage();
+
+ // Convert the image to ARGB so ImageIO writes it out nicely
+ BufferedImage argb = new BufferedImage(image.getWidth(), image.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = argb.createGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ return argb;
+ }
+
+ private boolean writeToFileHelper(String path, String format) {
+ BufferedImage argb = convertSnapshot();
+
+ try {
+ ImageIO.write(argb, format, new File(path));
+ } catch (IOException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean sameAs(IMonkeyImage other, double percent) {
+ BufferedImage otherImage = other.getBufferedImage();
+ BufferedImage myImage = getBufferedImage();
+
+ // Easy size check
+ if (otherImage.getWidth() != myImage.getWidth()) {
+ return false;
+ }
+ if (otherImage.getHeight() != myImage.getHeight()) {
+ return false;
+ }
+
+ int[] otherPixel = new int[1];
+ int[] myPixel = new int[1];
+
+ int width = myImage.getWidth();
+ int height = myImage.getHeight();
+
+ int numDiffPixels = 0;
+ // Now, go through pixel-by-pixel and check that the images are the same;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
+ numDiffPixels++;
+ }
+ }
+ }
+ double numberPixels = (height * width);
+ double diffPercent = numDiffPixels / numberPixels;
+ return percent <= 1.0 - diffPercent;
+ }
+
+ // TODO: figure out the location of this class and is superclasses
+ private static class BufferedImageMonkeyImage extends MonkeyImageBase {
+ private final BufferedImage image;
+
+ public BufferedImageMonkeyImage(BufferedImage image) {
+ this.image = image;
+ }
+
+ @Override
+ public BufferedImage createBufferedImage() {
+ return image;
+ }
+ }
+
+ public static IMonkeyImage loadImageFromFile(String path) {
+ File f = new File(path);
+ if (f.exists() && f.canRead()) {
+ try {
+ BufferedImage bufferedImage = ImageIO.read(new File(path));
+ if (bufferedImage == null) {
+ LOG.log(Level.WARNING, "Cannot decode file %s", path);
+ return null;
+ }
+ return new BufferedImageMonkeyImage(bufferedImage);
+ } catch (IOException e) {
+ LOG.log(Level.WARNING, "Exception trying to decode image", e);
+ return null;
+ }
+ } else {
+ LOG.log(Level.WARNING, "Cannot read file %s", path);
+ return null;
+ }
+ }
+
+ @Override
+ public IMonkeyImage getSubImage(int x, int y, int w, int h) {
+ BufferedImage image = getBufferedImage();
+ return new BufferedImageMonkeyImage(image.getSubimage(x, y, w, h));
+ }
+}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/easy/By.java b/monkeyrunner/src/com/android/monkeyrunner/easy/By.java
index a8be6c0..1ed1c6f 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/easy/By.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/easy/By.java
@@ -39,6 +39,7 @@ import org.python.core.PyObject;
* without notice.
*
* TODO: Implement other selectors, like classid, hash, and so on.
+ * TODO: separate java-only core from jython wrapper
*/
public class By extends PyObject implements ClassDictInit {
public static void classDictInit(PyObject dict) {
@@ -62,6 +63,10 @@ public class By extends PyObject implements ClassDictInit {
return new By(id);
}
+ public static By id(String id) {
+ return new By(id);
+ }
+
/**
* Find the selected view from the root view node.
*/
diff --git a/monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java
index 8e6ec0f..e72e462 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java
@@ -22,7 +22,7 @@ import com.android.hierarchyviewerlib.device.ViewNode;
import com.android.hierarchyviewerlib.device.ViewNode.Property;
import com.android.monkeyrunner.JythonUtils;
import com.android.monkeyrunner.MonkeyDevice;
-import com.android.monkeyrunner.MonkeyDevice.TouchPressType;
+import com.android.monkeyrunner.core.IMonkeyDevice.TouchPressType;
import com.android.monkeyrunner.doc.MonkeyRunnerExported;
import org.eclipse.swt.graphics.Point;
@@ -32,6 +32,7 @@ import org.python.core.Py;
import org.python.core.PyException;
import org.python.core.PyInteger;
import org.python.core.PyObject;
+import org.python.core.PyString;
import org.python.core.PyTuple;
import java.util.Set;
@@ -61,7 +62,7 @@ public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
argDocs = { "MonkeyDevice to extend." })
public EasyMonkeyDevice(MonkeyDevice device) {
this.mDevice = device;
- this.mHierarchyViewer = device.getHierarchyViewer();
+ this.mHierarchyViewer = device.getImpl().getHierarchyViewer();
}
@MonkeyRunnerExported(doc = "Sends a touch event to the selected object.",
@@ -74,19 +75,16 @@ public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
By selector = getSelector(ap, 0);
- ViewNode node = mHierarchyViewer.findView(selector);
- if (node == null) {
- throw new PyException(Py.ValueError,
- String.format("View not found: %s", selector));
- }
- Point p = HierarchyViewer.getAbsoluteCenterOfView(node);
-
- PyObject[] otherArgs = new PyObject[3];
- otherArgs[0] = new PyInteger(p.x);
- otherArgs[1] = new PyInteger(p.y);
- otherArgs[2] = args[1];
+ String tmpType = ap.getString(1);
+ TouchPressType type = MonkeyDevice.TOUCH_NAME_TO_ENUM.get(tmpType);
+ if (type == null) type = TouchPressType.DOWN_AND_UP;
+ // TODO: try catch rethrow PyExc
+ touch(selector, type);
+ }
- mDevice.touch(otherArgs, kws);
+ public void touch(By selector, TouchPressType type) {
+ Point p = getElementCenter(selector);
+ mDevice.getImpl().touch(p.x, p.y, type);
}
@MonkeyRunnerExported(doc = "Types a string into the specified object.",
@@ -100,16 +98,13 @@ public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
By selector = getSelector(ap, 0);
String text = ap.getString(1);
+ type(selector, text);
+ }
- ViewNode node = mHierarchyViewer.findView(selector);
- if (node == null) {
- throw new PyException(Py.ValueError,
- String.format("View not found: %s", selector));
- }
-
- Point p = HierarchyViewer.getAbsoluteCenterOfView(node);
- mDevice.touch(p.x, p.y, TouchPressType.DOWN_AND_UP);
- mDevice.type(text);
+ public void type(By selector, String text) {
+ Point p = getElementCenter(selector);
+ mDevice.getImpl().touch(p.x, p.y, TouchPressType.DOWN_AND_UP);
+ mDevice.getImpl().type(text);
}
@MonkeyRunnerExported(doc = "Locates the coordinates of the selected object.",
@@ -141,7 +136,10 @@ public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
By selector = getSelector(ap, 0);
+ return exists(selector);
+ }
+ public boolean exists(By selector) {
ViewNode node = mHierarchyViewer.findView(selector);
return node != null;
}
@@ -155,13 +153,11 @@ public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
By selector = getSelector(ap, 0);
+ return visible(selector);
+ }
- ViewNode node = mHierarchyViewer.findView(selector);
- boolean ret = (node != null)
- && node.namedProperties.containsKey("getVisibility()")
- && "VISIBLE".equalsIgnoreCase(
- node.namedProperties.get("getVisibility()").value);
- return ret;
+ public boolean visible(By selector) {
+ return mHierarchyViewer.visible(selector);
}
@MonkeyRunnerExported(doc = "Obtain the text in the selected input box.",
@@ -173,21 +169,20 @@ public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
Preconditions.checkNotNull(ap);
By selector = getSelector(ap, 0);
+ return getText(selector);
+ }
- ViewNode node = mHierarchyViewer.findView(selector);
- if (node == null) {
- throw new RuntimeException("Node not found");
- }
- Property textProperty = node.namedProperties.get("text:mText");
- if (textProperty == null) {
- throw new RuntimeException("No text property on node");
- }
- return textProperty.value;
+ public String getText(By selector) {
+ return mHierarchyViewer.getText(selector);
}
@MonkeyRunnerExported(doc = "Gets the id of the focused window.",
returns = "The symbolic id of the focused window or None.")
public String getFocusedWindowId(PyObject[] args, String[] kws) {
+ return getFocusedWindowId();
+ }
+
+ public String getFocusedWindowId() {
return mHierarchyViewer.getFocusedWindowName();
}
@@ -210,6 +205,24 @@ public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
* @return selector object.
*/
private By getSelector(ArgParser ap, int i) {
- return (By)ap.getPyObject(0).__tojava__(By.class);
+ return (By)ap.getPyObject(i).__tojava__(By.class);
}
+
+ /**
+ * Get the coordinates of the element's center.
+ *
+ * @param selector the element selector
+ * @return the (x,y) coordinates of the center
+ */
+ private Point getElementCenter(By selector) {
+ ViewNode node = mHierarchyViewer.findView(selector);
+ if (node == null) {
+ throw new PyException(Py.ValueError,
+ String.format("View not found: %s", selector));
+ }
+
+ Point p = HierarchyViewer.getAbsoluteCenterOfView(node);
+ return p;
+ }
+
} \ No newline at end of file
diff --git a/monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java b/monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java
index 5d6911e..450571c 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java
@@ -121,4 +121,38 @@ public class HierarchyViewer {
return new Point(
point.x + (node.width / 2), point.y + (node.height / 2));
}
+
+ /**
+ * Gets the visibility of a given element.
+ *
+ * @param selector selector for the view.
+ * @return True if the element is visible.
+ */
+ public boolean visible(By selector) {
+ ViewNode node = findView(selector);
+ boolean ret = (node != null)
+ && node.namedProperties.containsKey("getVisibility()")
+ && "VISIBLE".equalsIgnoreCase(
+ node.namedProperties.get("getVisibility()").value);
+ return ret;
+
+ }
+
+ /**
+ * Gets the text of a given element.
+ *
+ * @param selector selector for the view.
+ * @return the text of the given element.
+ */
+ public String getText(By selector) {
+ ViewNode node = findView(selector);
+ if (node == null) {
+ throw new RuntimeException("Node not found");
+ }
+ ViewNode.Property textProperty = node.namedProperties.get("text:mText");
+ if (textProperty == null) {
+ throw new RuntimeException("No text property on node");
+ }
+ return textProperty.value;
+ }
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java
index c1a8f7f..914a5b9 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java
@@ -15,8 +15,9 @@
*/
package com.android.monkeyrunner.recorder;
-import com.android.monkeyrunner.MonkeyDevice;
import com.android.monkeyrunner.adb.AdbBackend;
+import com.android.monkeyrunner.core.IMonkeyBackend;
+import com.android.monkeyrunner.core.IMonkeyDevice;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
@@ -44,7 +45,7 @@ public class MonkeyRecorder {
*
* @param device
*/
- public static void start(final MonkeyDevice device) {
+ public static void start(final IMonkeyDevice device) {
MonkeyRecorderFrame frame = new MonkeyRecorderFrame(device);
// TODO: this is a hack until the window listener works.
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
@@ -69,7 +70,7 @@ public class MonkeyRecorder {
}
public static void main(String[] args) {
- AdbBackend adb = new AdbBackend();
+ IMonkeyBackend adb = new AdbBackend();
MonkeyRecorder.start(adb.waitForConnection());
}
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java
index b6c1f78..88c1e16 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java
@@ -16,7 +16,8 @@
package com.android.monkeyrunner.recorder;
import com.android.monkeyrunner.MonkeyDevice;
-import com.android.monkeyrunner.MonkeyImage;
+import com.android.monkeyrunner.core.IMonkeyImage;
+import com.android.monkeyrunner.core.IMonkeyDevice;
import com.android.monkeyrunner.recorder.actions.Action;
import com.android.monkeyrunner.recorder.actions.DragAction;
import com.android.monkeyrunner.recorder.actions.DragAction.Direction;
@@ -60,7 +61,7 @@ public class MonkeyRecorderFrame extends JFrame {
private static final Logger LOG =
Logger.getLogger(MonkeyRecorderFrame.class.getName());
- private final MonkeyDevice device;
+ private final IMonkeyDevice device;
private static final long serialVersionUID = 1L;
private JPanel jContentPane = null;
@@ -83,6 +84,7 @@ public class MonkeyRecorderFrame extends JFrame {
private ActionListModel actionListModel;
private final Timer refreshTimer = new Timer(1000, new ActionListener() {
+ @Override
public void actionPerformed(ActionEvent e) {
refreshDisplay(); // @jve:decl-index=0:
}
@@ -91,7 +93,7 @@ public class MonkeyRecorderFrame extends JFrame {
/**
* This is the default constructor
*/
- public MonkeyRecorderFrame(MonkeyDevice device) {
+ public MonkeyRecorderFrame(IMonkeyDevice device) {
this.device = device;
initialize();
}
@@ -110,7 +112,7 @@ public class MonkeyRecorderFrame extends JFrame {
}
private void refreshDisplay() {
- MonkeyImage snapshot = device.takeSnapshot();
+ IMonkeyImage snapshot = device.takeSnapshot();
currentImage = snapshot.createBufferedImage();
Graphics2D g = scaledImage.createGraphics();
@@ -200,6 +202,7 @@ public class MonkeyRecorderFrame extends JFrame {
waitButton = new JButton();
waitButton.setText("Wait");
waitButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
public void actionPerformed(java.awt.event.ActionEvent e) {
String howLongStr = JOptionPane.showInputDialog("How many seconds to wait?");
if (howLongStr != null) {
@@ -222,6 +225,7 @@ public class MonkeyRecorderFrame extends JFrame {
pressButton = new JButton();
pressButton.setText("Press a Button");
pressButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
public void actionPerformed(java.awt.event.ActionEvent e) {
JPanel panel = new JPanel();
JLabel text = new JLabel("What button to press?");
@@ -255,6 +259,7 @@ public class MonkeyRecorderFrame extends JFrame {
typeButton = new JButton();
typeButton.setText("Type Something");
typeButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
public void actionPerformed(java.awt.event.ActionEvent e) {
String whatToType = JOptionPane.showInputDialog("What to type?");
if (whatToType != null) {
@@ -276,6 +281,7 @@ public class MonkeyRecorderFrame extends JFrame {
flingButton = new JButton();
flingButton.setText("Fling");
flingButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
public void actionPerformed(java.awt.event.ActionEvent e) {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
@@ -358,6 +364,7 @@ public class MonkeyRecorderFrame extends JFrame {
exportActionButton = new JButton();
exportActionButton.setText("Export Actions");
exportActionButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
public void actionPerformed(java.awt.event.ActionEvent ev) {
JFileChooser fc = new JFileChooser();
if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
@@ -383,6 +390,7 @@ public class MonkeyRecorderFrame extends JFrame {
refreshButton = new JButton();
refreshButton.setText("Refresh Display");
refreshButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
public void actionPerformed(java.awt.event.ActionEvent e) {
refreshDisplay();
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java
index d582aa4..6fa91ab 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java
@@ -15,7 +15,7 @@
*/
package com.android.monkeyrunner.recorder.actions;
-import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyDevice;
/**
* All actions that can be recorded must implement this interface.
@@ -41,5 +41,5 @@ public interface Action {
*
* @param device the device to execute the action on.
*/
- void execute(MonkeyDevice device) throws Exception;
+ void execute(IMonkeyDevice device) throws Exception;
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java
index 082bfe4..2461c0d 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java
@@ -15,7 +15,7 @@
*/
package com.android.monkeyrunner.recorder.actions;
-import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyDevice;
/**
* Action to drag the "finger" across the device.
@@ -77,7 +77,7 @@ public class DragAction implements Action {
}
@Override
- public void execute(MonkeyDevice device) {
+ public void execute(IMonkeyDevice device) {
device.drag(startx, starty, endx, endy, steps, timeMs);
}
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java
index a0d9e0e..66a933a 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java
@@ -19,6 +19,7 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyDevice;
/**
* Action to press a certain button.
@@ -60,7 +61,7 @@ public class PressAction implements Action {
}
@Override
- public void execute(MonkeyDevice device) {
+ public void execute(IMonkeyDevice device) {
device.press(key,
MonkeyDevice.TOUCH_NAME_TO_ENUM.get(downUpFlag));
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java
index 4633edb..4e0ae2d 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java
@@ -19,6 +19,7 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyDevice;
/**
* Action to touch the touchscreen at a certain location.
@@ -46,7 +47,7 @@ public class TouchAction implements Action {
}
@Override
- public void execute(MonkeyDevice device) throws Exception {
+ public void execute(IMonkeyDevice device) throws Exception {
device.touch(x, y,
MonkeyDevice.TOUCH_NAME_TO_ENUM.get(direction));
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java
index 1bfb9e9..78e90b0 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java
@@ -15,7 +15,7 @@
*/
package com.android.monkeyrunner.recorder.actions;
-import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyDevice;
/**
* Action to type in a string on the device.
@@ -34,12 +34,13 @@ public class TypeAction implements Action {
@Override
public String serialize() {
- String pydict = PyDictUtilBuilder.newBuilder().add("message", whatToType).build();
+ String pydict = PyDictUtilBuilder.newBuilder()
+ .add("message", whatToType).build();
return "TYPE|" + pydict;
}
@Override
- public void execute(MonkeyDevice device) {
+ public void execute(IMonkeyDevice device) {
device.type(whatToType);
}
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java
index 9115f9a..bd2d421 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java
@@ -15,7 +15,7 @@
*/
package com.android.monkeyrunner.recorder.actions;
-import com.android.monkeyrunner.MonkeyDevice;
+import com.android.monkeyrunner.core.IMonkeyDevice;
/**
* Action that specifies to wait for a certain amount of time.
@@ -27,19 +27,16 @@ public class WaitAction implements Action {
this.howLongSeconds = howLongSeconds;
}
- @Override
public String getDisplayName() {
return String.format("Wait for %g seconds", this.howLongSeconds);
}
- @Override
public String serialize() {
String pydict = PyDictUtilBuilder.newBuilder().add("seconds", howLongSeconds).build();
return "WAIT|" + pydict;
}
- @Override
- public void execute(MonkeyDevice device) throws Exception {
+ public void execute(IMonkeyDevice device) throws Exception {
long ms = (long) (1000.0f * howLongSeconds);
Thread.sleep(ms);
}
diff --git a/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java b/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
index c2fa5f7..b868bf1 100644
--- a/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
+++ b/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
@@ -15,18 +15,22 @@
*/
package com.android.monkeyrunner.stub;
-import com.android.monkeyrunner.MonkeyDevice;
import com.android.monkeyrunner.MonkeyManager;
-import com.android.monkeyrunner.MonkeyRunnerBackend;
-
-public class StubBackend implements MonkeyRunnerBackend {
+import com.android.monkeyrunner.core.IMonkeyBackend;
+import com.android.monkeyrunner.core.IMonkeyDevice;
+public class StubBackend implements IMonkeyBackend {
public MonkeyManager createManager(String address, int port) {
// TODO Auto-generated method stub
return null;
}
- public MonkeyDevice waitForConnection(long timeout, String deviceId) {
+ public IMonkeyDevice waitForConnection() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public IMonkeyDevice waitForConnection(long timeout, String deviceId) {
// TODO Auto-generated method stub
return null;
}
diff --git a/ninepatch/tests/Android.mk b/ninepatch/tests/Android.mk
index 51a55f0..8a9fd71 100644
--- a/ninepatch/tests/Android.mk
+++ b/ninepatch/tests/Android.mk
@@ -18,6 +18,7 @@ include $(CLEAR_VARS)
# Only compile source java files in this lib.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := res
LOCAL_MODULE := ninepatch-tests
LOCAL_MODULE_TAGS := optional
diff --git a/ninepatch/tests/src/com/android/ninepatch/button.9.png b/ninepatch/tests/res/com/android/ninepatch/button.9.png
index 9d52f40..9d52f40 100644
--- a/ninepatch/tests/src/com/android/ninepatch/button.9.png
+++ b/ninepatch/tests/res/com/android/ninepatch/button.9.png
Binary files differ