aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java91
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java20
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java277
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java172
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java121
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java (renamed from apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java)434
-rw-r--r--apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java795
-rw-r--r--apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java412
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java968
-rw-r--r--apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java74
-rw-r--r--apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java142
-rw-r--r--apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java213
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java205
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java17
14 files changed, 2071 insertions, 1870 deletions
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
index f22f12f..06b29f1 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
@@ -30,15 +30,16 @@ import android.view.View.OnTouchListener;
import android.widget.TextView;
import com.android.tools.sdkcontroller.R;
-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;
+import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.lib.ProtocolConstants;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
import com.android.tools.sdkcontroller.utils.ApiHelper;
import com.android.tools.sdkcontroller.views.MultiTouchView;
/**
- * Activity that controls and displays the {@link MultiTouchHandler}.
+ * Activity that controls and displays the {@link MultiTouchChannel}.
*/
public class MultiTouchActivity extends BaseBindingActivity
implements android.os.Handler.Callback {
@@ -47,14 +48,7 @@ public class MultiTouchActivity extends BaseBindingActivity
private static String TAG = MultiTouchActivity.class.getSimpleName();
private static boolean DEBUG = true;
- /** Received frame is JPEG image. */
- private static final int FRAME_JPEG = 1;
- /** Received frame is RGB565 bitmap. */
- private static final int FRAME_RGB565 = 2;
- /** Received frame is RGB888 bitmap. */
- private static final int FRAME_RGB888 = 3;
-
- private volatile MultiTouchHandler mHandler;
+ private volatile MultiTouchChannel mHandler;
private TextView mTextError;
private TextView mTextStatus;
@@ -108,8 +102,9 @@ public class MultiTouchActivity extends BaseBindingActivity
@Override
protected void onServiceConnected() {
if (DEBUG) Log.d(TAG, "onServiceConnected");
- mHandler = (MultiTouchHandler) getServiceBinder().getHandler(HandlerType.MultiTouch);
+ mHandler = (MultiTouchChannel) getServiceBinder().getChannel(Channel.MULTITOUCH_CHANNEL);
if (mHandler != null) {
+ mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight());
mHandler.addUiHandler(mUiHandler);
}
}
@@ -150,7 +145,7 @@ public class MultiTouchActivity extends BaseBindingActivity
if (binder != null) {
boolean connected = binder.isEmuConnected();
mImageView.setEnabled(connected);
- updateStatus(connected ? "Emulated connected" : "Emulator disconnected");
+ updateStatus(connected ? "Emulator connected" : "Emulator disconnected");
}
}
});
@@ -169,43 +164,67 @@ public class MultiTouchActivity extends BaseBindingActivity
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
- StringBuilder sb = new StringBuilder();
+ ByteBuffer bb = null;
final int action = event.getAction();
final int action_code = action & MotionEvent.ACTION_MASK;
final int action_pid_index = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
+ int msg_type = 0;
+ MultiTouchChannel h = mHandler;
// Build message for the emulator.
switch (action_code) {
case MotionEvent.ACTION_MOVE:
- sb.append("action=move");
- for (int n = 0; n < event.getPointerCount(); n++) {
- mImageView.constructEventMessage(sb, event, n);
+ if (h != null) {
+ bb = ByteBuffer.allocate(
+ event.getPointerCount() * ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ for (int n = 0; n < event.getPointerCount(); n++) {
+ mImageView.constructEventMessage(bb, event, n);
+ }
+ msg_type = ProtocolConstants.MT_MOVE;
}
break;
case MotionEvent.ACTION_DOWN:
- sb.append("action=down");
- mImageView.constructEventMessage(sb, event, action_pid_index);
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ mImageView.constructEventMessage(bb, event, action_pid_index);
+ msg_type = ProtocolConstants.MT_FISRT_DOWN;
+ }
break;
case MotionEvent.ACTION_UP:
- sb.append("action=up pid=").append(event.getPointerId(action_pid_index));
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ bb.putInt(event.getPointerId(action_pid_index));
+ msg_type = ProtocolConstants.MT_LAST_UP;
+ }
break;
case MotionEvent.ACTION_POINTER_DOWN:
- sb.append("action=pdown");
- mImageView.constructEventMessage(sb, event, action_pid_index);
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ mImageView.constructEventMessage(bb, event, action_pid_index);
+ msg_type = ProtocolConstants.MT_POINTER_DOWN;
+ }
break;
case MotionEvent.ACTION_POINTER_UP:
- sb.append("action=pup pid=").append(event.getPointerId(action_pid_index));
+ if (h != null) {
+ bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);
+ bb.order(h.getEndian());
+ bb.putInt(event.getPointerId(action_pid_index));
+ msg_type = ProtocolConstants.MT_POINTER_UP;
+ }
break;
default:
Log.w(TAG, "Unknown action type: " + action_code);
return true;
}
- if (DEBUG) Log.d(TAG, sb.toString());
+ if (DEBUG && bb != null) Log.d(TAG, bb.toString());
- MultiTouchHandler h = mHandler;
- if (h != null) {
- h.sendEventToEmulator(sb.toString() + '\0');
+ if (h != null && bb != null) {
+ h.postMessage(msg_type, bb);
}
return true;
}
@@ -215,18 +234,18 @@ public class MultiTouchActivity extends BaseBindingActivity
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
- case MultiTouchHandler.EVENT_MT_START:
- MultiTouchHandler h = mHandler;
+ case MultiTouchChannel.EVENT_MT_START:
+ MultiTouchChannel h = mHandler;
if (h != null) {
- mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight());
+ mImageView.setEnabled(true);
mImageView.setOnTouchListener(mTouchListener);
}
break;
- case MultiTouchHandler.EVENT_MT_STOP:
+ case MultiTouchChannel.EVENT_MT_STOP:
mImageView.setOnTouchListener(null);
break;
- case MultiTouchHandler.EVENT_FRAME_BUFFER:
- onFrameBuffer((byte[]) msg.obj);
+ case MultiTouchChannel.EVENT_FRAME_BUFFER:
+ onFrameBuffer(((ByteBuffer) msg.obj).array());
break;
}
return true; // we consumed this message
@@ -267,7 +286,7 @@ public class MultiTouchActivity extends BaseBindingActivity
// Update application display.
updateDisplay(disp_width, disp_height);
- if (format == FRAME_JPEG) {
+ if (format == ProtocolConstants.MT_FRAME_JPEG) {
/*
* Framebuffer is in JPEG format.
*/
@@ -293,7 +312,7 @@ public class MultiTouchActivity extends BaseBindingActivity
}
// Convert the blob bitmap into bitmap that we will display.
- if (format == FRAME_RGB565) {
+ if (format == ProtocolConstants.MT_FRAME_RGB565) {
for (int n = 0; n < pixel_num; n++) {
// Blob bitmap is in RGB565 format.
final int color = bb.getShort();
@@ -302,7 +321,7 @@ public class MultiTouchActivity extends BaseBindingActivity
final int b = ((color & 0x1f) << 3) | ((color & 0x1f) >> 2);
mColors[n] = Color.rgb(r, g, b);
}
- } else if (format == FRAME_RGB888) {
+ } else if (format == ProtocolConstants.MT_FRAME_RGB888) {
for (int n = 0; n < pixel_num; n++) {
// Blob bitmap is in RGB565 format.
final int r = bb.getChar();
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
index 5055c23..001398b 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
@@ -35,14 +35,14 @@ import android.widget.TableRow;
import android.widget.TextView;
import com.android.tools.sdkcontroller.R;
-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.SensorsHandler;
-import com.android.tools.sdkcontroller.handlers.SensorsHandler.MonitoredSensor;
+import com.android.tools.sdkcontroller.handlers.SensorChannel;
+import com.android.tools.sdkcontroller.handlers.SensorChannel.MonitoredSensor;
+import com.android.tools.sdkcontroller.lib.Channel;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;
import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;
/**
- * Activity that displays and controls the sensors from {@link SensorsHandler}.
+ * Activity that displays and controls the sensors from {@link SensorChannel}.
* For each sensor it displays a checkbox that is enabled if the sensor is supported
* by the emulator. The user can select whether the sensor is active. It also displays
* data from the sensor when available.
@@ -61,10 +61,10 @@ public class SensorActivity extends BaseBindingActivity
private TextView mTextStatus;
private TextView mTextTargetHz;
private TextView mTextActualHz;
- private SensorsHandler mSensorHandler;
+ private SensorChannel mSensorHandler;
private final Map<MonitoredSensor, DisplayInfo> mDisplayedSensors =
- new HashMap<SensorsHandler.MonitoredSensor, SensorActivity.DisplayInfo>();
+ new HashMap<SensorChannel.MonitoredSensor, SensorActivity.DisplayInfo>();
private final android.os.Handler mUiHandler = new android.os.Handler(this);
private int mTargetSampleRate;
private long mLastActualUpdateMs;
@@ -173,7 +173,7 @@ public class SensorActivity extends BaseBindingActivity
removeSensorUi();
}
- mSensorHandler = (SensorsHandler) getServiceBinder().getHandler(HandlerType.Sensor);
+ mSensorHandler = (SensorChannel) getServiceBinder().getChannel(Channel.SENSOR_CHANNEL);
if (mSensorHandler != null) {
mSensorHandler.addUiHandler(mUiHandler);
mUiHandler.sendEmptyMessage(MSG_UPDATE_ACTUAL_HZ);
@@ -261,19 +261,19 @@ public class SensorActivity extends BaseBindingActivity
public boolean handleMessage(Message msg) {
DisplayInfo info = null;
switch (msg.what) {
- case SensorsHandler.SENSOR_STATE_CHANGED:
+ case SensorChannel.SENSOR_STATE_CHANGED:
info = mDisplayedSensors.get(msg.obj);
if (info != null) {
info.updateState();
}
break;
- case SensorsHandler.SENSOR_DISPLAY_MODIFIED:
+ case SensorChannel.SENSOR_DISPLAY_MODIFIED:
info = mDisplayedSensors.get(msg.obj);
if (info != null) {
info.updateValue();
}
if (mSensorHandler != null) {
- updateStatus(Integer.toString(mSensorHandler.getEventSentCount()) + " events sent");
+ updateStatus(Integer.toString(mSensorHandler.getMsgSentCount()) + " events sent");
// Update the "actual rate" field if the value has changed
long ms = mSensorHandler.getActualUpdateMs();
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
deleted file mode 100755
index b15b8c1..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2012 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.tools.sdkcontroller.handlers;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import android.content.Context;
-import android.os.Message;
-import android.util.Log;
-
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
-import com.android.tools.sdkcontroller.lib.EmulatorListener;
-import com.android.tools.sdkcontroller.service.ControllerService;
-
-
-/**
- * An abstract base class for all "action handlers".
- * <p/>
- * The {@link ControllerService} can deal with several handlers, each have a specific
- * purpose as described by {@link HandlerType}.
- * <p/>
- * The {@link BaseHandler} class adds support for activities to connect by providing
- * an {@link android.os.Handler} (which we'll call a "UI Handler" to differentiate it
- * from our "Service Handler"). The service handler will provide events via this
- * UI handler directly on the activity's UI thread.
- * <p/>
- * The {@link BaseHandler} keeps track of the current {@link EmulatorConnection} given
- * via {@link #onStart(EmulatorConnection, Context)}.
- * <p/>
- * The {@link BaseHandler} provides a simple way for activities to send event messages
- * back to the emulator by using {@link #sendEventToEmulator(String)}. This method
- * is safe to call from any thread, even the UI thread.
- */
-public abstract class BaseHandler {
-
- protected static final boolean DEBUG = false;
- protected static final String TAG = null;
-
- private EmulatorConnection mConnection;
-
- private final AtomicInteger mEventCount = new AtomicInteger(0);
- private volatile boolean mRunEventQueue = true;
- private final BlockingQueue<String> mEventQueue = new LinkedBlockingQueue<String>();
- private static String EVENT_QUEUE_END = "@end@";
- private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();
- private final HandlerType mHandlerType;
- private final Thread mEventThread;
- private int mPort;
-
- /**
- * The type of action that this handler manages.
- */
- public enum HandlerType {
- /** A handler to send multitouch events from the device to the emulator and display
- * the emulator screen on the device. */
- MultiTouch,
- /** A handler to send sensor events from the device to the emulaotr. */
- Sensor
- }
-
- /**
- * Initializes a new base handler.
- *
- * @param type A non-null {@link HandlerType} value.
- * @param port A non-null communication port number.
- */
- protected BaseHandler(HandlerType type, int port) {
- mHandlerType = type;
- mPort = port;
-
- final String name = type.toString();
- mEventThread = new Thread(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "EventThread.started-" + name);
- while(mRunEventQueue) {
- try {
- String msg = mEventQueue.take();
- if (msg != null && mConnection != null && !msg.equals(EVENT_QUEUE_END)) {
- mConnection.sendNotification(msg);
- mEventCount.incrementAndGet();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "EventThread-" + name, e);
- }
- }
- if (DEBUG) Log.d(TAG, "EventThread.terminate-" + name);
- }
- }, "EventThread-" + name);
- }
-
- /**
- * Returns the type of this handler, as given to the constructor.
- *
- * @return One of the {@link HandlerType} values.
- */
- public HandlerType getType() {
- return mHandlerType;
- }
-
- /**
- * Returns he communication port used by this handler to communicate with the emulator,
- * as given to the constructor.
- * <p/>
- * Note that right now we have 2 handlers that each use their own port. The goal is
- * to move to a single-connection mechanism where all the handlers' data will be
- * multiplexed on top of a single {@link EmulatorConnection}.
- *
- * @return A non-null port value.
- */
- public int getPort() {
- return mPort;
- }
-
- /**
- * Returns the last {@link EmulatorConnection} passed to
- * {@link #onStart(EmulatorConnection, Context)}.
- * It becomes null when {@link #onStop()} is called.
- *
- * @return The current {@link EmulatorConnection}.
- */
- public EmulatorConnection getConnection() {
- return mConnection;
- }
-
- /**
- * Called once the {@link EmulatorConnection} has been successfully initialized.
- * <p/>
- * Note that this will <em>not</em> be called if the {@link EmulatorConnection}
- * fails to bind to the underlying socket.
- * <p/>
- * This base implementation keeps tracks of the connection.
- *
- * @param connection The connection that has just been created.
- * A handler might want to use this to send data to the emulator via
- * {@link EmulatorConnection#sendNotification(String)}. However handlers
- * need to be particularly careful in <em>not</em> sending network data
- * from the main UI thread.
- * @param context The controller service' context.
- * @see #getConnection()
- */
- public void onStart(EmulatorConnection connection, Context context) {
- assert connection != null;
- mConnection = connection;
- mRunEventQueue = true;
- mEventThread.start();
- }
-
- /**
- * Called once the {@link EmulatorConnection} is being disconnected.
- * This nullifies the connection returned by {@link #getConnection()}.
- */
- public void onStop() {
- // Stop the message queue
- mConnection = null;
- if (mRunEventQueue) {
- mRunEventQueue = false;
- mEventQueue.offer(EVENT_QUEUE_END);
- }
- }
-
- public int getEventSentCount() {
- return mEventCount.get();
- }
-
- /**
- * Utility for handlers or activities to sends a string event to the emulator.
- * This method is safe for the activity to call from any thread, including the UI thread.
- *
- * @param msg Event message. Must not be null.
- */
- public void sendEventToEmulator(String msg) {
- try {
- mEventQueue.put(msg);
- } catch (InterruptedException e) {
- Log.e(TAG, "EventQueue.put", e);
- }
- }
-
- // ------------
- // Interaction from the emulator connection towards the handler
-
- /**
- * Emulator query being forwarded to the handler.
- *
- * @see EmulatorListener#onEmulatorQuery(String, String)
- */
- public abstract String onEmulatorQuery(String query, String param);
-
- /**
- * Emulator blob query being forwarded to the handler.
- *
- * @see EmulatorListener#onEmulatorBlobQuery(byte[])
- */
- public abstract String onEmulatorBlobQuery(byte[] array);
-
- // ------------
- // Interaction from handler towards listening UI
-
- /**
- * Indicates any UI handler is currently registered with the handler.
- * If no UI is displaying the handler's state, maybe the handler can skip UI related tasks.
- *
- * @return True if there's at least one UI handler registered.
- */
- public boolean hasUiHandler() {
- return !mUiHandlers.isEmpty();
- }
-
- /**
- * Registers a new UI handler.
- *
- * @param uiHandler A non-null UI handler to register.
- * Ignored if the UI handler is null or already registered.
- */
- public void addUiHandler(android.os.Handler uiHandler) {
- assert uiHandler != null;
- if (uiHandler != null) {
- if (!mUiHandlers.contains(uiHandler)) {
- mUiHandlers.add(uiHandler);
- }
- }
- }
-
- /**
- * Unregisters an UI handler.
- *
- * @param uiHandler A non-null UI listener to unregister.
- * Ignored if the listener is null or already registered.
- */
- public void removeUiHandler(android.os.Handler uiHandler) {
- assert uiHandler != null;
- mUiHandlers.remove(uiHandler);
- }
-
- /**
- * Protected method to be used by handlers to send an event to all UI handlers.
- *
- * @param event An integer event code with no specific parameters.
- * To be defined by the handler itself.
- */
- protected void notifyUiHandlers(int event) {
- for (android.os.Handler uiHandler : mUiHandlers) {
- uiHandler.sendEmptyMessage(event);
- }
- }
-
- /**
- * Protected method to be used by handlers to send an event to all UI handlers.
- *
- * @param msg An event with parameters. To be defined by the handler itself.
- */
- protected void notifyUiHandlers(Message msg) {
- for (android.os.Handler uiHandler : mUiHandlers) {
- uiHandler.sendMessage(msg);
- }
- }
-
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
new file mode 100755
index 0000000..fc70ae1
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.handlers;
+
+import android.graphics.Point;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.lib.ProtocolConstants;
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implements multi-touch emulation.
+ */
+public class MultiTouchChannel extends Channel {
+
+ @SuppressWarnings("hiding")
+ private static final String TAG = MultiTouchChannel.class.getSimpleName();
+ /**
+ * A new frame buffer has been received from the emulator.
+ * Parameter {@code obj} is a {@code byte[] array} containing the screen data.
+ */
+ public static final int EVENT_FRAME_BUFFER = 1;
+ /**
+ * A multi-touch "start" command has been received from the emulator.
+ * Parameter {@code obj} is the string parameter from the start command.
+ */
+ public static final int EVENT_MT_START = 2;
+ /**
+ * A multi-touch "stop" command has been received from the emulator. There
+ * is no {@code obj} parameter associated.
+ */
+ public static final int EVENT_MT_STOP = 3;
+
+ private static final Point mViewSize = new Point(0, 0);
+
+ /**
+ * Constructs MultiTouchChannel instance.
+ */
+ public MultiTouchChannel(ControllerService service) {
+ super(service, Channel.MULTITOUCH_CHANNEL);
+ }
+
+ /**
+ * Sets size of the display view for emulated screen updates.
+ *
+ * @param width View width in pixels.
+ * @param height View height in pixels.
+ */
+ public void setViewSize(int width, int height) {
+ mViewSize.set(width, height);
+ }
+
+ /*
+ * Channel abstract implementation.
+ */
+
+ /**
+ * This method is invoked when this channel is fully connected with its
+ * counterpart in the emulator.
+ */
+ @Override
+ public void onEmulatorConnected() {
+ if (hasUiHandler()) {
+ enable();
+ notifyUiHandlers(EVENT_MT_START);
+ }
+ }
+
+ /**
+ * This method is invoked when this channel loses connection with its
+ * counterpart in the emulator.
+ */
+ @Override
+ public void onEmulatorDisconnected() {
+ if (hasUiHandler()) {
+ disable();
+ notifyUiHandlers(EVENT_MT_STOP);
+ }
+ }
+
+ /**
+ * A message has been received from the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg_data Packet received from the emulator.
+ */
+ @Override
+ public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {
+ switch (msg_type) {
+ case ProtocolConstants.MT_FB_UPDATE:
+ Message msg = Message.obtain();
+ msg.what = EVENT_FRAME_BUFFER;
+ msg.obj = msg_data;
+ notifyUiHandlers(msg);
+ break;
+
+ default:
+ Log.e(TAG, "Unknown message type " + msg_type);
+ }
+ }
+
+ /**
+ * A query has been received from the emulator.
+ *
+ * @param query_id Identifies the query. This ID must be used when replying
+ * to the query.
+ * @param query_type Query type.
+ * @param query_data Query data.
+ */
+ @Override
+ public void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data) {
+ Loge("Unexpected query " + query_type + " in multi-touch");
+ sendQueryResponse(query_id, (byte[]) null);
+ }
+
+ /**
+ * Registers a new UI handler.
+ *
+ * @param uiHandler A non-null UI handler to register. Ignored if the UI
+ * handler is null or already registered.
+ */
+ @Override
+ public void addUiHandler(android.os.Handler uiHandler) {
+ final boolean first_handler = !hasUiHandler();
+ super.addUiHandler(uiHandler);
+ if (first_handler && isConnected()) {
+ enable();
+ notifyUiHandlers(EVENT_MT_START);
+ }
+ }
+
+ /**
+ * Unregisters an UI handler.
+ *
+ * @param uiHandler A non-null UI listener to unregister. Ignored if the
+ * listener is null or already registered.
+ */
+ @Override
+ public void removeUiHandler(android.os.Handler uiHandler) {
+ super.removeUiHandler(uiHandler);
+ if (isConnected() && !hasUiHandler()) {
+ disable();
+ }
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
deleted file mode 100755
index 6f64485..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2012 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.tools.sdkcontroller.handlers;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.os.Message;
-import android.util.Log;
-
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
-
-
-public class MultiTouchHandler extends BaseHandler {
-
- @SuppressWarnings("hiding")
- private static final String TAG = MultiTouchHandler.class.getSimpleName();
- /**
- * A new frame buffer has been received from the emulator.
- * Parameter {@code obj} is a {@code byte[] array} containing the screen data.
- */
- public static final int EVENT_FRAME_BUFFER = 1;
- /**
- * A multi-touch "start" command has been received from the emulator.
- * Parameter {@code obj} is the string parameter from the start command.
- */
- public static final int EVENT_MT_START = 2;
- /**
- * A multi-touch "stop" command has been received from the emulator.
- * There is no {@code obj} parameter associated.
- */
- public static final int EVENT_MT_STOP = 3;
-
- private static final Point mViewSize = new Point(0, 0);
-
- public MultiTouchHandler() {
- super(HandlerType.MultiTouch, EmulatorConnection.MULTITOUCH_PORT);
- }
-
- public void setViewSize(int width, int height) {
- mViewSize.set(width, height);
- }
-
- @Override
- public void onStart(EmulatorConnection connection, Context context) {
- super.onStart(connection, context);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- }
-
- /**
- * Called when a query is received from the emulator. NOTE: This method is
- * called from the I/O loop.
- *
- * @param query Name of the query received from the emulator. The allowed
- * queries are: - 'start' - Starts delivering touch screen events
- * to the emulator. - 'stop' - Stops delivering touch screen
- * events to the emulator.
- * @param param Query parameters.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
- */
- @Override
- public String onEmulatorQuery(String query, String param) {
- if (query.contentEquals("start")) {
- Message msg = Message.obtain();
- msg.what = EVENT_MT_START;
- msg.obj = param;
- notifyUiHandlers(msg);
- return "ok:" + mViewSize.x + "x" + mViewSize.y + "\0";
-
- } else if (query.contentEquals("stop")) {
- notifyUiHandlers(EVENT_MT_STOP);
- return "ok\0";
-
- } else {
- Log.e(TAG, "Unknown query " + query + "(" + param + ")");
- return "ko:Unknown query\0";
- }
- }
-
- /**
- * Called when a BLOB query is received from the emulator.
- * <p/>
- * This query is used to deliver framebuffer updates in the emulator. The
- * blob contains an update header, followed by the bitmap containing updated
- * rectangle. The header is defined as MTFrameHeader structure in
- * external/qemu/android/multitouch-port.h
- * <p/>
- * NOTE: This method is called from the I/O loop, so all communication with
- * the emulator will be "on hold" until this method returns.
- *
- * @param array contains BLOB data for the query.
- * @return Empty string: this query doesn't require any response.
- */
- @Override
- public String onEmulatorBlobQuery(byte[] array) {
- Message msg = Message.obtain();
- msg.what = EVENT_FRAME_BUFFER;
- msg.obj = array;
- notifyUiHandlers(msg);
- return "";
- }
-
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
index 498b86d..d1ab836 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
@@ -16,6 +16,7 @@
package com.android.tools.sdkcontroller.handlers;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -28,13 +29,17 @@ import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.lib.ProtocolConstants;
+import com.android.tools.sdkcontroller.service.ControllerService;
-
-public class SensorsHandler extends BaseHandler {
+/**
+ * Implements sensors emulation.
+ */
+public class SensorChannel extends Channel {
@SuppressWarnings("hiding")
- private static String TAG = SensorsHandler.class.getSimpleName();
+ private static String TAG = SensorChannel.class.getSimpleName();
@SuppressWarnings("hiding")
private static boolean DEBUG = false;
/**
@@ -46,31 +51,63 @@ public class SensorsHandler extends BaseHandler {
* Default value should match res/values/strings.xml > sensors_default_sample_rate.
*/
private long mUpdateTargetMs = 1000/20; // 20 fps in milliseconds
+ /** Accumulates average update frequency. */
private long mGlobalAvgUpdateMs = 0;
+ /** Array containing monitored sensors. */
+ private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>();
+ /** Sensor manager. */
+ private SensorManager mSenMan;
+
+ /*
+ * Messages exchanged with the UI.
+ */
/**
- * Sensor "enabled by emulator" state has changed.
- * Parameter {@code obj} is the {@link MonitoredSensor}.
+ * Sensor "enabled by emulator" state has changed. Parameter {@code obj} is
+ * the {@link MonitoredSensor}.
*/
public static final int SENSOR_STATE_CHANGED = 1;
/**
- * Sensor display value has changed.
- * Parameter {@code obj} is the {@link MonitoredSensor}.
+ * Sensor display value has changed. Parameter {@code obj} is the
+ * {@link MonitoredSensor}.
*/
public static final int SENSOR_DISPLAY_MODIFIED = 2;
- /** Array containing monitored sensors. */
- private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>();
- private SensorManager mSenMan;
+ /**
+ * Constructs SensorChannel instance.
+ *
+ * @param service Service context.
+ */
+ public SensorChannel(ControllerService service) {
+ super(service, Channel.SENSOR_CHANNEL);
+ mSenMan = (SensorManager) service.getSystemService(Context.SENSOR_SERVICE);
+ // Iterate through the available sensors, adding them to the array.
+ List<Sensor> sensors = mSenMan.getSensorList(Sensor.TYPE_ALL);
+ int cur_index = 0;
+ for (int n = 0; n < sensors.size(); n++) {
+ Sensor avail_sensor = sensors.get(n);
- public SensorsHandler() {
- super(HandlerType.Sensor, EmulatorConnection.SENSORS_PORT);
+ // There can be multiple sensors of the same type. We need only one.
+ if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {
+ // The first sensor we've got for the given type is not
+ // necessarily the right one. So, use the default sensor
+ // for the given type.
+ Sensor def_sens = mSenMan.getDefaultSensor(avail_sensor.getType());
+ MonitoredSensor to_add = new MonitoredSensor(def_sens);
+ cur_index++;
+ mSensors.add(to_add);
+ if (DEBUG)
+ Log.d(TAG, String.format(
+ "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
+ cur_index, def_sens.getName(), def_sens.getType()));
+ }
+ }
}
/**
* Returns the list of sensors found on the device.
- * The list is computed once by {@link #onStart(EmulatorConnection, Context)}.
+ * The list is computed once by {@link #SensorChannel(ControllerService)}.
*
* @return A non-null possibly-empty list of sensors.
*/
@@ -100,188 +137,146 @@ public class SensorsHandler extends BaseHandler {
return mGlobalAvgUpdateMs;
}
- @Override
- public void onStart(EmulatorConnection connection, Context context) {
- super.onStart(connection, context);
-
- // Iterate through the available sensors, adding them to the array.
- SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- mSenMan = sm;
- List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ALL);
- int cur_index = 0;
- for (int n = 0; n < sensors.size(); n++) {
- Sensor avail_sensor = sensors.get(n);
-
- // There can be multiple sensors of the same type. We need only one.
- if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {
- // The first sensor we've got for the given type is not
- // necessarily the right one. So, use the default sensor
- // for the given type.
- Sensor def_sens = sm.getDefaultSensor(avail_sensor.getType());
- MonitoredSensor to_add = new MonitoredSensor(def_sens);
- cur_index++;
- mSensors.add(to_add);
- if (DEBUG) Log.d(TAG, String.format(
- "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
- cur_index, def_sens.getName(), def_sens.getType()));
- }
- }
- }
-
- @Override
- public void onStop() {
- stopSensors();
- super.onStop();
- }
+ /*
+ * Channel abstract implementation.
+ */
/**
- * Called when a query is received from the emulator. NOTE: This method is
- * called from the I/O loop.
- *
- * @param query Name of the query received from the emulator. The allowed
- * queries are: 'list' - Lists sensors that are monitored by this
- * application. The application replies to this command with a
- * string: 'List:<name1>\n<name2>\n...<nameN>\n\0" 'start' -
- * Starts monitoring sensors. There is no reply for this command.
- * 'stop' - Stops monitoring sensors. There is no reply for this
- * command. 'enable:<sensor|all> - Enables notifications for a
- * sensor / all sensors. 'disable:<sensor|all> - Disables
- * notifications for a sensor / all sensors.
- * @param param Query parameters.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
+ * This method is invoked when this channel is fully connected with its
+ * counterpart in the emulator.
*/
@Override
- public String onEmulatorQuery(String query, String param) {
- if (query.contentEquals("list")) {
- return onQueryList();
- } else if (query.contentEquals("start")) {
- return onQueryStart();
- } else if (query.contentEquals("stop")) {
- return onQueryStop();
- } else if (query.contentEquals("enable")) {
- return onQueryEnable(param);
- } else if (query.contentEquals("disable")) {
- return onQueryDisable(param);
- } else {
- Log.e(TAG, "Unknown query " + query + "(" + param + ")");
- return "ko:Query is unknown\0";
- }
+ public void onEmulatorConnected() {
+ // Emulation is now possible. Note though that it will start only after
+ // emulator tells us so with SENSORS_START command.
+ enable();
}
/**
- * Called when a BLOB query is received from the emulator. NOTE: This method
- * is called from the I/O loop, so all communication with the emulator will
- * be "on hold" until this method returns.
- *
- * @param array contains BLOB data for the query.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
+ * This method is invoked when this channel loses connection with its
+ * counterpart in the emulator.
*/
@Override
- public String onEmulatorBlobQuery(byte[] array) {
- return "ko:Unexpected\0";
+ public void onEmulatorDisconnected() {
+ // Stop sensor event callbacks.
+ stopSensors();
}
- /***************************************************************************
- * Query handlers
- **************************************************************************/
-
/**
- * Handles 'list' query.
+ * A query has been received from the emulator.
*
- * @return List of emulator-friendly names for sensors that are available on
- * the device.
+ * @param query_id Identifies the query. This ID should be used when
+ * replying to the query.
+ * @param query_type Query type.
+ * @param query_data Query data.
*/
- private String onQueryList() {
- // List monitored sensors.
- String list = "ok:";
- for (MonitoredSensor sensor : mSensors) {
- list += sensor.getEmulatorFriendlyName();
- list += "\n";
+ @Override
+ public void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data) {
+ switch (query_type) {
+ case ProtocolConstants.SENSORS_QUERY_LIST:
+ // Preallocate large response buffer.
+ ByteBuffer resp = ByteBuffer.allocate(1024);
+ resp.order(getEndian());
+ // Iterate through the list of monitored sensors, dumping them
+ // into the response buffer.
+ for (MonitoredSensor sensor : mSensors) {
+ // Entry for each sensor must contain:
+ // - an integer for its ID
+ // - a zero-terminated emulator-friendly name.
+ final byte[] name = sensor.getEmulatorFriendlyName().getBytes();
+ final int required_size = 4 + name.length + 1;
+ resp = ExpandIf(resp, required_size);
+ resp.putInt(sensor.getType());
+ resp.put(name);
+ resp.put((byte) 0);
+ }
+ // Terminating entry contains single -1 integer.
+ resp = ExpandIf(resp, 4);
+ resp.putInt(-1);
+ sendQueryResponse(query_id, resp);
+ return;
+
+ default:
+ Loge("Unknown query " + query_type);
+ return;
}
- list += '\0'; // Response must end with zero-terminator.
- return list;
- }
-
- /**
- * Handles 'start' query.
- *
- * @return Empty string. This is a "command" query that doesn't assume any
- * response.
- */
- private String onQueryStart() {
- startSensors();
- return "ok\0";
}
/**
- * Handles 'stop' query.
+ * A message has been received from the emulator.
*
- * @return Empty string. This is a "command" query that doesn't assume any
- * response.
+ * @param msg_type Message type.
+ * @param msg_data Packet received from the emulator.
*/
- private String onQueryStop() {
- stopSensors();
- return "ok\0";
+ @Override
+ public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {
+ switch (msg_type) {
+ case ProtocolConstants.SENSORS_START:
+ Log.v(TAG, "Starting sensors emulation.");
+ startSensors();
+ break;
+ case ProtocolConstants.SENSORS_STOP:
+ Log.v(TAG, "Stopping sensors emulation.");
+ stopSensors();
+ break;
+ case ProtocolConstants.SENSORS_ENABLE:
+ String enable_name = new String(msg_data.array());
+ Log.v(TAG, "Enabling sensor: " + enable_name);
+ onEnableSensor(enable_name);
+ break;
+ case ProtocolConstants.SENSORS_DISABLE:
+ String disable_name = new String(msg_data.array());
+ Log.v(TAG, "Disabling sensor: " + disable_name);
+ onDisableSensor(disable_name);
+ break;
+ default:
+ Loge("Unknown message type " + msg_type);
+ break;
+ }
}
/**
- * Handles 'enable' query.
+ * Handles 'enable' message.
*
- * @param param Sensor selector: - all Enables all available sensors, or -
- * <name> Emulator-friendly name of a sensor to enable.
- * @return "ok" / "ko": success / failure.
+ * @param name Emulator-friendly name of a sensor to enable, or "all" to
+ * enable all sensors.
*/
- private String onQueryEnable(String param) {
- if (param.contentEquals("all")) {
+ private void onEnableSensor(String name) {
+ if (name.contentEquals("all")) {
// Enable all sensors.
for (MonitoredSensor sensor : mSensors) {
sensor.enableSensor();
}
- return "ok\0";
- }
-
- // Lookup sensor by emulator-friendly name.
- MonitoredSensor sensor = getSensorByEFN(param);
- if (sensor != null) {
- sensor.enableSensor();
- return "ok\0";
} else {
- return "ko:Sensor not found\0";
+ // Lookup sensor by emulator-friendly name.
+ final MonitoredSensor sensor = getSensorByEFN(name);
+ if (sensor != null) {
+ sensor.enableSensor();
+ }
}
}
/**
- * Handles 'disable' query.
+ * Handles 'disable' message.
*
- * @param param Sensor selector: - all Disables all available sensors, or -
- * <name> Emulator-friendly name of a sensor to disable.
- * @return "ok" / "ko": success / failure.
+ * @param name Emulator-friendly name of a sensor to disable, or "all" to
+ * disable all sensors.
*/
- private String onQueryDisable(String param) {
- if (param.contentEquals("all")) {
+ private void onDisableSensor(String name) {
+ if (name.contentEquals("all")) {
// Disable all sensors.
for (MonitoredSensor sensor : mSensors) {
sensor.disableSensor();
}
- return "ok\0";
- }
-
- // Lookup sensor by emulator-friendly name.
- MonitoredSensor sensor = getSensorByEFN(param);
- if (sensor != null) {
- sensor.disableSensor();
- return "ok\0";
} else {
- return "ko:Sensor not found\0";
+ // Lookup sensor by emulator-friendly name.
+ MonitoredSensor sensor = getSensorByEFN(name);
+ if (sensor != null) {
+ sensor.disableSensor();
+ }
}
}
- /***************************************************************************
- * Internals
- **************************************************************************/
-
/**
* Start listening to all monitored sensors.
*/
@@ -300,6 +295,10 @@ public class SensorsHandler extends BaseHandler {
}
}
+ /***************************************************************************
+ * Internals
+ **************************************************************************/
+
/**
* Checks if a sensor for the given type is already monitored.
*
@@ -351,8 +350,7 @@ public class SensorsHandler extends BaseHandler {
private String mEmulatorFriendlyName;
/** Formats string to show in the TextView. */
private String mTextFmt;
- private int mExpectedLen;
- private int mNbValues = 0;
+ /** Sensor values. */
private float[] mValues = new float[3];
/**
* Enabled state. This state is controlled by the emulator, that
@@ -362,6 +360,7 @@ public class SensorsHandler extends BaseHandler {
private boolean mEnabledByEmulator = false;
/** User-controlled enabled state. */
private boolean mEnabledByUser = true;
+ /** Sensor event listener for this sensor. */
private final OurSensorEventListener mListener = new OurSensorEventListener();
/**
@@ -377,124 +376,112 @@ public class SensorsHandler extends BaseHandler {
// we can't really use sensor.getName() here, since the value it
// returns (although resembles the purpose) is a bit vaguer than it
// should be. Also choose an appropriate format for the strings that
- // display sensor's value, and strings that are sent to the
- // emulator.
+ // display sensor's value.
switch (sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
mUiName = "Accelerometer";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "acceleration";
- mExpectedLen = 3;
break;
case 9: // Sensor.TYPE_GRAVITY is missing in API 7
- // 3 floats.
mUiName = "Gravity";
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "gravity";
- mExpectedLen = 3;
break;
case Sensor.TYPE_GYROSCOPE:
mUiName = "Gyroscope";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "gyroscope";
- mExpectedLen = 3;
break;
case Sensor.TYPE_LIGHT:
mUiName = "Light";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "light";
- mExpectedLen = 1;
break;
case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7
mUiName = "Linear acceleration";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "linear-acceleration";
- mExpectedLen = 3;
break;
case Sensor.TYPE_MAGNETIC_FIELD:
mUiName = "Magnetic field";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "magnetic-field";
- mExpectedLen = 3;
break;
case Sensor.TYPE_ORIENTATION:
mUiName = "Orientation";
- // 3 integers.
mTextFmt = "%+03.0f %+03.0f %+03.0f";
mEmulatorFriendlyName = "orientation";
- mExpectedLen = 3;
break;
case Sensor.TYPE_PRESSURE:
mUiName = "Pressure";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "pressure";
- mExpectedLen = 1;
break;
case Sensor.TYPE_PROXIMITY:
mUiName = "Proximity";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "proximity";
- mExpectedLen = 1;
break;
case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
mUiName = "Rotation";
- // 3 floats.
mTextFmt = "%+.2f %+.2f %+.2f";
mEmulatorFriendlyName = "rotation";
- mExpectedLen = 3;
break;
case Sensor.TYPE_TEMPERATURE:
mUiName = "Temperature";
- // 1 integer.
mTextFmt = "%.0f";
mEmulatorFriendlyName = "temperature";
- mExpectedLen = 1;
break;
default:
mUiName = "<Unknown>";
mTextFmt = "N/A";
mEmulatorFriendlyName = "unknown";
- mExpectedLen = 0;
- if (DEBUG) Log.e(TAG, "Unknown sensor type " + mSensor.getType() +
+ if (DEBUG) Loge("Unknown sensor type " + mSensor.getType() +
" for sensor " + mSensor.getName());
break;
}
}
+ /**
+ * Get name for this sensor to display.
+ *
+ * @return Name for this sensor to display.
+ */
public String getUiName() {
return mUiName;
}
+ /**
+ * Gets current sensor value to display.
+ *
+ * @return Current sensor value to display.
+ */
public String getValue() {
- String val = mValue;
-
- if (val == null) {
- int len = mNbValues;
+ if (mValue == null) {
float[] values = mValues;
- if (len == 3) {
- val = String.format(mTextFmt, values[0], values[1],values[2]);
- } else if (len == 2) {
- val = String.format(mTextFmt, values[0], values[1]);
- } else if (len == 1) {
- val = String.format(mTextFmt, values[0]);
- }
- mValue = val;
+ mValue = String.format(mTextFmt, values[0], values[1], values[2]);
}
-
- return val == null ? "??" : val;
+ return mValue == null ? "??" : mValue;
}
+ /**
+ * Checks if monitoring of this this sensor has been enabled by
+ * emulator.
+ *
+ * @return true if monitoring of this this sensor has been enabled by
+ * emulator, or false if emulator didn't enable this sensor.
+ */
public boolean isEnabledByEmulator() {
return mEnabledByEmulator;
}
+ /**
+ * Checks if monitoring of this this sensor has been enabled by user.
+ *
+ * @return true if monitoring of this this sensor has been enabled by
+ * user, or false if user didn't enable this sensor.
+ */
public boolean isEnabledByUser() {
return mEnabledByUser;
}
@@ -513,8 +500,6 @@ public class SensorsHandler extends BaseHandler {
}
}
- // ---------
-
/**
* Gets sensor type.
*
@@ -560,7 +545,6 @@ public class SensorsHandler extends BaseHandler {
private void enableSensor() {
if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
mEnabledByEmulator = true;
- mNbValues = 0;
mValue = null;
Message msg = Message.obtain();
@@ -586,10 +570,11 @@ public class SensorsHandler extends BaseHandler {
private class OurSensorEventListener implements SensorEventListener {
/** Last update's time-stamp in local thread millisecond time. */
- private long mLastUpdateTS;
+ private long mLastUpdateTS = 0;
/** Last display update time-stamp. */
- private long mLastDisplayTS;
- private final StringBuilder mTempStr = new StringBuilder();
+ private long mLastDisplayTS = 0;
+ /** Preallocated buffer for change notification message. */
+ private final ByteBuffer mChangeMsg = ByteBuffer.allocate(64);
/**
* Handles "sensor changed" event.
@@ -597,7 +582,7 @@ public class SensorsHandler extends BaseHandler {
*/
@Override
public void onSensorChanged(SensorEvent event) {
- long now = SystemClock.currentThreadTimeMillis();
+ long now = SystemClock.elapsedRealtime();
long deltaMs = 0;
if (mLastUpdateTS != 0) {
@@ -608,31 +593,21 @@ public class SensorsHandler extends BaseHandler {
}
}
- // Format message that will be sent to the emulator.
+ // Format and post message for the emulator.
float[] values = event.values;
final int len = values.length;
- // A 3printfs with 3 * %g takes around 9-15 ms on an ADP2, or 3-4 ms on a GN.
- // However doing 3 * StringBuilder.append(float) takes < ~1 ms on ADP2.
- StringBuilder sb = mTempStr;
- sb.setLength(0);
- sb.append(mEmulatorFriendlyName);
-
- if (len != mExpectedLen) {
- Log.e(TAG, "Unexpected number of values " + len
- + " in onSensorChanged for sensor " + mSensor.getName());
- return;
- } else {
- sb.append(':').append(values[0]);
- if (len > 1) {
- sb.append(':').append(values[1]);
- if (len > 2) {
- sb.append(':').append(values[2]);
- }
+ mChangeMsg.order(getEndian());
+ mChangeMsg.position(0);
+ mChangeMsg.putInt(getType());
+ mChangeMsg.putFloat(values[0]);
+ if (len > 1) {
+ mChangeMsg.putFloat(values[1]);
+ if (len > 2) {
+ mChangeMsg.putFloat(values[2]);
}
}
- sb.append('\0');
- sendEventToEmulator(sb.toString());
+ postMessage(ProtocolConstants.SENSORS_SENSOR_EVENT, mChangeMsg);
// Computes average update time for this sensor and average globally.
if (mLastUpdateTS != 0) {
@@ -648,14 +623,13 @@ public class SensorsHandler extends BaseHandler {
if (hasUiHandler()) {
if (mLastDisplayTS != 0) {
long uiDeltaMs = now - mLastDisplayTS;
- if (uiDeltaMs < 1000/4 /*4fps in ms*/) {
+ if (uiDeltaMs < 1000 / 4 /* 4fps in ms */) {
// Skip this UI update
return;
}
}
mLastDisplayTS = now;
- mNbValues = len;
mValues[0] = values[0];
if (len > 1) {
mValues[1] = values[1];
@@ -672,7 +646,7 @@ public class SensorsHandler extends BaseHandler {
}
if (DEBUG) {
- long now2 = SystemClock.currentThreadTimeMillis();
+ long now2 = SystemClock.elapsedRealtime();
long processingTimeMs = now2 - now;
Log.d(TAG, String.format("glob %d - local %d > target %d - processing %d -- %s",
mGlobalAvgUpdateMs, deltaMs, mUpdateTargetMs, processingTimeMs,
@@ -681,8 +655,8 @@ public class SensorsHandler extends BaseHandler {
}
/**
- * Handles "sensor accuracy changed" event. This is an implementation of
- * the SensorEventListener interface.
+ * Handles "sensor accuracy changed" event.
+ * This is an implementation of the SensorEventListener interface.
*/
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
@@ -690,4 +664,12 @@ public class SensorsHandler extends BaseHandler {
}
} // MonitoredSensor
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
new file mode 100644
index 0000000..639f4cf
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.lib;
+
+import android.os.Message;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Encapsulates basics of a connection with the emulator.
+ * This class must be used as a base class for all the channelss that provide
+ * particular type of emulation (such as sensors, multi-touch, etc.)
+ * <p/>
+ * Essentially, Channel is an implementation of a particular emulated functionality,
+ * that defines logical format of the data transferred between the emulator and
+ * SDK controller. For instance, "sensors" is a channel that emulates sensors,
+ * and transfers sensor value changes from the device to the emulator. "Multi-touch"
+ * is a channel that supports multi-touch emulation, and transfers multi-touch
+ * events to the emulator, while receiving frame buffer updates from the emulator.
+ * <p/>
+ * Besides connection with the emulator, each channel may contain one or more UI
+ * components associated with it. This class provides some basics for UI support,
+ * including:
+ * <p/>
+ * - Providing a way to register / unregister a UI component with the channel.
+ * <p/>
+ * - Implementing posting of messages to emulator in opposite to direct message
+ * sent. This is due to requirement that UI threads are prohibited from doing
+ * network I/O.
+ */
+public abstract class Channel {
+
+ /**
+ * Encapsulates a message posted to be sent to the emulator from a worker
+ * thread. This class is used to describe a message that is posted in UI
+ * thread, and then picked up in the worker thread.
+ */
+ private class SdkControllerMessage {
+ /** Message type. */
+ private int mMessageType;
+ /** Message data (can be null). */
+ private byte[] mMessage;
+ /** Message data size */
+ private int mMessageSize;
+
+ /**
+ * Construct message from an array.
+ *
+ * @param type Message type.
+ * @param message Message data. Message data size is defined by size of
+ * the array.
+ */
+ public SdkControllerMessage(int type, byte[] message) {
+ mMessageType = type;
+ mMessage = message;
+ mMessageSize = (message != null) ? message.length : 0;
+ }
+
+ /**
+ * Construct message from a ByteBuffer.
+ *
+ * @param type Message type.
+ * @param message Message data. Message data size is defined by
+ * position() property of the ByteBuffer.
+ */
+ public SdkControllerMessage(int type, ByteBuffer message) {
+ mMessageType = type;
+ if (message != null) {
+ mMessage = message.array();
+ mMessageSize = message.position();
+ } else {
+ mMessage = null;
+ mMessageSize = 0;
+ }
+ }
+
+ /**
+ * Gets message type.
+
+ *
+ * @return Message type.
+ */
+ public int getMessageType() {
+ return mMessageType;
+ }
+
+ /**
+ * Gets message buffer.
+ *
+ * @return Message buffer.
+ */
+ public byte[] getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Gets message buffer size.
+ *
+ * @return Message buffer size.
+ */
+ public int getMessageSize() {
+ return mMessageSize;
+ }
+ } // SdkControllerMessage
+
+ /*
+ * Names for currently implemented SDK controller channels.
+ */
+
+ /** Name for a channel that handles sensors emulation */
+ public static final String SENSOR_CHANNEL = "sensors";
+ /** Name for a channel that handles multi-touch emulation */
+ public static final String MULTITOUCH_CHANNEL = "multi-touch";
+
+ /*
+ * Types of messages internally used by Channel class.
+ */
+
+ /** Service-side emulator is connected. */
+ private static final int MSG_CONNECTED = -1;
+ /** Service-side emulator is disconnected. */
+ private static final int MSG_DISCONNECTED = -2;
+ /** Service-side emulator is enabled. */
+ private static final int MSG_ENABLED = -3;
+ /** Service-side emulator is disabled. */
+ private static final int MSG_DISABLED = -4;
+
+ /** Tag for logging messages. */
+ private static final String TAG = "SdkControllerChannel";
+ /** Controls debug log. */
+ private static final boolean DEBUG = false;
+
+ /** Service that has created this object. */
+ protected ControllerService mService;
+
+ /*
+ * Socket stuff.
+ */
+
+ /** Socket to use to to communicate with the emulator. */
+ private Socket mSocket = null;
+ /** Channel name ("sensors", "multi-touch", etc.) */
+ private String mChannelName;
+ /** Endianness of data transferred in this channel. */
+ private ByteOrder mEndian;
+
+ /*
+ * Message posting support.
+ */
+
+ /** Total number of messages posted in this channel */
+ private final AtomicInteger mMsgCount = new AtomicInteger(0);
+ /** Flags whether or not message thread is running. */
+ private volatile boolean mRunMsgQueue = true;
+ /** Queue of messages pending transmission. */
+ private final BlockingQueue<SdkControllerMessage>
+ mMsgQueue = new LinkedBlockingQueue<SdkControllerMessage>();
+ /** Message thread */
+ private final Thread mMsgThread;
+
+ /*
+ * UI support.
+ */
+
+ /** Lists UI handlers attached to this channel. */
+ private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();
+
+ /*
+ * Abstract methods.
+ */
+
+ /**
+ * This method is invoked when this channel is fully connected with its
+ * counterpart in the emulator.
+ */
+ public abstract void onEmulatorConnected();
+
+ /**
+ * This method is invoked when this channel loses connection with its
+ * counterpart in the emulator.
+ */
+ public abstract void onEmulatorDisconnected();
+
+ /**
+ * A message has been received from the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg_data Message data. Message data size is defined by the length
+ * of the array wrapped by the ByteBuffer.
+ */
+ public abstract void onEmulatorMessage(int msg_type, ByteBuffer msg_data);
+
+ /**
+ * A query has been received from the emulator.
+ *
+ * @param query_id Identifies the query. This ID must be used when replying
+ * to the query.
+ * @param query_type Query type.
+ * @param query_data Query data. Query data size is defined by the length of
+ * the array wrapped by the ByteBuffer.
+ */
+ public abstract void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data);
+
+ /*
+ * Channel implementation.
+ */
+
+ /**
+ * Constructs Channel instance.
+ *
+ * @param name Channel name.
+ */
+ public Channel(ControllerService service, String name) {
+ mService = service;
+ mChannelName = name;
+ // Start the worker thread for posted messages.
+ mMsgThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "MsgThread.started-" + mChannelName);
+ while (mRunMsgQueue) {
+ try {
+ SdkControllerMessage msg = mMsgQueue.take();
+ if (msg != null) {
+ sendMessage(
+ msg.getMessageType(), msg.getMessage(), msg.getMessageSize());
+ mMsgCount.incrementAndGet();
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "MsgThread-" + mChannelName, e);
+ }
+ }
+ if (DEBUG) Log.d(TAG, "MsgThread.terminate-" + mChannelName);
+ }
+ }, "MsgThread-" + name);
+ mMsgThread.start();
+ if (DEBUG) Log.d(TAG, "Channel is constructed for " + mChannelName);
+ }
+
+ /**
+ * Gets name for this channel.
+ *
+ * @return Emulator name.
+ */
+ public String getChannelName() {
+ return mChannelName;
+ }
+
+ /**
+ * Gets endianness for this channel.
+ *
+ * @return Channel endianness.
+ */
+ public ByteOrder getEndian() {
+ return mEndian;
+ }
+
+ /**
+ * Gets number of messages sent via postMessage method.
+ *
+ * @return Number of messages sent via postMessage method.
+ */
+ public int getMsgSentCount() {
+ return mMsgCount.get();
+ }
+
+ /**
+ * Checks if this channel is connected with the emulator.
+ *
+ * @return true if this channel is connected with the emulator, or false if it is
+ * not connected.
+ */
+ public boolean isConnected() {
+ // Use local copy of the socket, ensuring it's not going to NULL while
+ // we're working with it. If it gets closed, while we're in the middle
+ // of data transfer - it's OK, since it will produce an exception, and
+ // the caller will gracefully handle it.
+ //
+ // Same technique is used everywhere in this class where mSocket member
+ // is touched.
+ Socket socket = mSocket;
+ return socket != null && socket.isConnected();
+ }
+
+ /**
+ * Establishes connection with the emulator. This method is called by Connection
+ * object when emulator successfully connects to this channel, or this channel
+ * gets registered, and there is a pending socket connection for it.
+ *
+ * @param socket Channel connection socket.
+ */
+ public void connect(Socket socket) {
+ mSocket = socket;
+ mEndian = socket.getEndian();
+ Logv("Channel " + mChannelName + " is now connected with the emulator.");
+ // Notify the emulator that connection is established.
+ sendMessage(MSG_CONNECTED, (byte[]) null);
+
+ // Let the derived class know that emulator is connected, and start the
+ // I/O loop in which we will receive data from the emulator. Note that
+ // we start the loop after onEmulatorConnected call, since we don't want
+ // to start dispatching messages before the derived class could set
+ // itself up for receiving them.
+ onEmulatorConnected();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runIOLooper();
+ }
+ }, "ChannelIoLoop").start();
+ mService.notifyStatusChanged();
+ }
+
+ /**
+ * Disconnects this channel from the emulator.
+ *
+ * @return true if this channel has been disconnected in this call, or false if
+ * channel has been already disconnected when this method has been called.
+ */
+ public boolean disconnect() {
+ // This is the only place in this class where we will null the
+ // socket object. Since this method can be called concurrently from
+ // different threads, lets do this under the lock.
+ Socket socket;
+ synchronized (this) {
+ socket = mSocket;
+ mSocket = null;
+ }
+ if (socket != null) {
+ // Notify the emulator about channel disconnection before we close
+ // the communication socket.
+ try {
+ sendMessage(socket, MSG_DISCONNECTED, null, 0);
+ } catch (IOException e) {
+ // Ignore I/O exception at this point. We don't care about
+ // it, since the socket is being closed anyways.
+ }
+ // This will eventually stop I/O looper thread.
+ socket.close();
+ mService.notifyStatusChanged();
+ }
+ return socket != null;
+ }
+
+ /**
+ * Enables the emulation. Typically, this method is called for channels that are
+ * dependent on UI to handle the emulation. For instance, multi-touch emulation is
+ * disabled until at least one UI component is attached to the channel. So, for
+ * multi-touch emulation this method is called when UI gets attached to the channel.
+ */
+ public void enable() {
+ postMessage(MSG_ENABLED, (byte[]) null);
+ mService.notifyStatusChanged();
+ }
+
+ /**
+ * Disables the emulation. Just the opposite to enable(). For multi-touch this
+ * method is called when UI detaches from the channel.
+ */
+ public void disable() {
+ postMessage(MSG_DISABLED, (byte[]) null);
+ mService.notifyStatusChanged();
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param socket Socket to send the message to.
+ * @param msg_type Message type.
+ * @param msg Message data to send.
+ * @param len Byte size of message data.
+ * @throws IOException
+ */
+ private void sendMessage(Socket socket, int msg_type, byte[] msg, int len)
+ throws IOException {
+ // In async environment we must have message header and message data in
+ // one block to prevent messages from other threads getting between the
+ // header and the data. So, we can't sent header, and then the data. We
+ // must combine them in one data block instead.
+ ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
+ bb.order(mEndian);
+
+ // Initialize message header.
+ bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
+ bb.putInt(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
+ bb.putInt(ProtocolConstants.PACKET_TYPE_MESSAGE);
+ bb.putInt(msg_type);
+
+ // Save message data (if there is any).
+ if (len != 0) {
+ bb.put(msg, 0, len);
+ }
+
+ socket.send(bb.array());
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to send. Message size is defined by the size of
+ * the array.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendMessage(int msg_type, byte[] msg, int msg_len) {
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ sendMessage(socket, msg_type, msg, msg_len);
+ return true;
+ } else {
+ Logw("sendMessage is called on disconnected Channel " + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to send. Message size is defined by the size of
+ * the array.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendMessage(int msg_type, byte[] msg) {
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ if (msg != null) {
+ sendMessage(socket, msg_type, msg, msg.length);
+ } else {
+ sendMessage(socket, msg_type, null, 0);
+ }
+ return true;
+ } else {
+ Logw("sendMessage is called on disconnected Channel " + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Sends message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to send. Message size is defined by the
+ * position() property of the ByteBuffer.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendMessage(int msg_type, ByteBuffer msg) {
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ if (msg != null) {
+ sendMessage(socket, msg_type, msg.array(), msg.position());
+ } else {
+ sendMessage(socket, msg_type, null, 0);
+ }
+ return true;
+ } else {
+ Logw("sendMessage is called on disconnected Channel " + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Posts message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to post. Message size is defined by the size of
+ * the array.
+ */
+ public void postMessage(int msg_type, byte[] msg) {
+ try {
+ mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "mMessageQueue.put", e);
+ }
+ }
+
+ /**
+ * Posts message to the emulator.
+ *
+ * @param msg_type Message type.
+ * @param msg Message data to post. Message size is defined by the
+ * position() property of the ByteBuffer.
+ */
+ public void postMessage(int msg_type, ByteBuffer msg) {
+ try {
+ mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
+ } catch (InterruptedException e) {
+ Log.e(TAG, "mMessageQueue.put", e);
+ }
+ }
+
+ /**
+ * Sends query response to the emulator.
+ *
+ * @param query_id Query identifier.
+ * @param qresp Response to the query.
+ * @param len Byte size of query response data.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendQueryResponse(int query_id, byte[] qresp, int len) {
+ // Just like with messages, we must combine header and data in a single
+ // transmitting block.
+ ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
+ bb.order(mEndian);
+
+ // Initialize response header.
+ bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
+ bb.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
+ bb.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
+ bb.putInt(query_id);
+
+ // Save response data (if there is any).
+ if (qresp != null && len != 0) {
+ bb.put(qresp, 0, len);
+ }
+
+ // Send the response.
+ try {
+ Socket socket = mSocket;
+ if (socket != null) {
+ socket.send(bb.array());
+ return true;
+ } else {
+ Logw("sendQueryResponse is called on disconnected Channel "
+ + mChannelName);
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in sendQueryResponse for Channel " + mChannelName);
+ onIoFailure();
+ }
+ return false;
+ }
+
+ /**
+ * Sends query response to the emulator.
+ *
+ * @param query_id Query identifier.
+ * @param qresp Response to the query. Query response size is defined by the
+ * size of the array.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendQueryResponse(int query_id, byte[] qresp) {
+ return (qresp != null) ? sendQueryResponse(query_id, qresp, qresp.length) :
+ sendQueryResponse(query_id, null, 0);
+ }
+
+ /**
+ * Sends query response to the emulator.
+ *
+ * @param query_id Query identifier.
+ * @param qresp Response to the query. Query response size is defined by the
+ * position() property of the ByteBuffer.
+ * @return true on success, or false if data transmission has failed.
+ */
+ public boolean sendQueryResponse(int query_id, ByteBuffer qresp) {
+ return (qresp != null) ? sendQueryResponse(query_id, qresp.array(), qresp.position()) :
+ sendQueryResponse(query_id, null, 0);
+ }
+
+ /**
+ * Handles an I/O failure occurred in the channel.
+ */
+ private void onIoFailure() {
+ // All I/O failures cause disconnection.
+ if (disconnect()) {
+ // Success of disconnect() indicates that I/O failure is not the
+ // result of a disconnection request, but is in deed an I/O
+ // failure. Report lost connection to the derived class.
+ Loge("Connection with the emulator has been lost in Channel " + mChannelName);
+ onEmulatorDisconnected();
+ }
+ }
+
+ /**
+ * Loops on the local socket, handling connection attempts.
+ */
+ private void runIOLooper() {
+ if (DEBUG) Log.d(TAG, "In I/O looper for Channel " + mChannelName);
+ // Initialize byte buffer large enough to receive packet header.
+ ByteBuffer header = ByteBuffer.allocate(ProtocolConstants.PACKET_HEADER_SIZE);
+ header.order(mEndian);
+ try {
+ // Since disconnection (which will null the mSocket) can be
+ // requested from outside of this thread, it's simpler just to make
+ // a copy of mSocket here, and work with that copy. Otherwise we
+ // will have to go through a complex synchronization algorithm that
+ // would decrease performance on normal runs. If socket gets closed
+ // while we're in the middle of transfer, an exception will occur,
+ // which we will catch and handle properly.
+ Socket socket = mSocket;
+ while (socket != null) {
+ // Reset header position.
+ header.position(0);
+ // This will receive total packet size + packet type.
+ socket.receive(header.array());
+ // First - signature.
+ final int signature = header.getInt();
+ assert signature == ProtocolConstants.PACKET_SIGNATURE;
+ // Next - packet size (including header).
+ int remains = header.getInt() - ProtocolConstants.PACKET_HEADER_SIZE;
+ // After the size comes packet type.
+ final int packet_type = header.getInt();
+
+ // Get the remainder of the data, and dispatch the packet to
+ // an appropriate handler.
+ switch (packet_type) {
+ case ProtocolConstants.PACKET_TYPE_MESSAGE:
+ // Read message header (one int: message type).
+ final int ext = ProtocolConstants.MESSAGE_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
+ header.position(0);
+ socket.receive(header.array(), ext);
+ final int msg_type = header.getInt();
+
+ // Read message data.
+ remains -= ext;
+ final ByteBuffer msg_data = ByteBuffer.allocate(remains);
+ msg_data.order(mEndian);
+ socket.receive(msg_data.array());
+
+ // Dispatch message for handling.
+ onEmulatorMessage(msg_type, msg_data);
+ break;
+
+ case ProtocolConstants.PACKET_TYPE_QUERY:
+ // Read query ID and query type.
+ final int extq = ProtocolConstants.QUERY_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
+ header.position(0);
+ socket.receive(header.array(), extq);
+ final int query_id = header.getInt();
+ final int query_type = header.getInt();
+
+ // Read query data.
+ remains -= extq;
+ final ByteBuffer query_data = ByteBuffer.allocate(remains);
+ query_data.order(mEndian);
+ socket.receive(query_data.array());
+
+ // Dispatch query for handling.
+ onEmulatorQuery(query_id, query_type, query_data);
+ break;
+
+ default:
+ // Unknown packet type. Just discard the remainder
+ // of the packet
+ Loge("Unknown packet type " + packet_type + " in Channel "
+ + mChannelName);
+ final byte[] discard_data = new byte[remains];
+ socket.receive(discard_data);
+ break;
+ }
+ socket = mSocket;
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + " in I/O looper for Channel " + mChannelName);
+ onIoFailure();
+ }
+ if (DEBUG) Log.d(TAG, "Exiting I/O looper for Channel " + mChannelName);
+ }
+
+ /**
+ * Indicates any UI handler is currently registered with the channel. If no UI
+ * is displaying the channel's state, maybe the channel can skip UI related tasks.
+ *
+ * @return True if there's at least one UI handler registered.
+ */
+ public boolean hasUiHandler() {
+ return !mUiHandlers.isEmpty();
+ }
+
+ /**
+ * Registers a new UI handler.
+ *
+ * @param uiHandler A non-null UI handler to register. Ignored if the UI
+ * handler is null or already registered.
+ */
+ public void addUiHandler(android.os.Handler uiHandler) {
+ assert uiHandler != null;
+ if (uiHandler != null) {
+ if (!mUiHandlers.contains(uiHandler)) {
+ mUiHandlers.add(uiHandler);
+ }
+ }
+ }
+
+ /**
+ * Unregisters an UI handler.
+ *
+ * @param uiHandler A non-null UI listener to unregister. Ignored if the
+ * listener is null or already registered.
+ */
+ public void removeUiHandler(android.os.Handler uiHandler) {
+ assert uiHandler != null;
+ mUiHandlers.remove(uiHandler);
+ }
+
+ /**
+ * Protected method to be used by handlers to send an event to all UI
+ * handlers.
+ *
+ * @param event An integer event code with no specific parameters. To be
+ * defined by the handler itself.
+ */
+ protected void notifyUiHandlers(int event) {
+ for (android.os.Handler uiHandler : mUiHandlers) {
+ uiHandler.sendEmptyMessage(event);
+ }
+ }
+
+ /**
+ * Protected method to be used by handlers to send an event to all UI
+ * handlers.
+ *
+ * @param msg An event with parameters. To be defined by the handler itself.
+ */
+ protected void notifyUiHandlers(Message msg) {
+ for (android.os.Handler uiHandler : mUiHandlers) {
+ uiHandler.sendMessage(msg);
+ }
+ }
+
+ /**
+ * A helper routine that expands ByteBuffer to contain given number of extra
+ * bytes.
+ *
+ * @param buff Buffer to expand.
+ * @param extra Number of bytes that are required to be available in the
+ * buffer after current position()
+ * @return ByteBuffer, containing required number of available bytes.
+ */
+ public ByteBuffer ExpandIf(ByteBuffer buff, int extra) {
+ if (extra <= buff.remaining()) {
+ return buff;
+ }
+ ByteBuffer ret = ByteBuffer.allocate(buff.position() + extra);
+ ret.order(buff.order());
+ ret.put(buff.array(), 0, buff.position());
+ return ret;
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
+
+ private void Logw(String log) {
+ Log.w(TAG, log);
+ }
+
+ private void Logv(String log) {
+ Log.v(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
new file mode 100644
index 0000000..cb50869
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.lib;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.util.Log;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+/**
+ * Encapsulates a connection between SdkController service and the emulator. On
+ * the device side, the connection is bound to the UNIX-domain socket named
+ * 'android.sdk.controller'. On the emulator side the connection is established
+ * via TCP port that is used to forward I/O traffic on the host machine to
+ * 'android.sdk.controller' socket on the device. Typically, the port forwarding
+ * can be enabled using adb command:
+ * <p/>
+ * 'adb forward tcp:<TCP port number> localabstract:android.sdk.controller'
+ * <p/>
+ * The way communication between the emulator and SDK controller service works
+ * is as follows:
+ * <p/>
+ * 1. Both sides, emulator and the service have components that implement a particular
+ * type of emulation. For instance, AndroidSensorsPort in the emulator, and
+ * SensorChannel in the application implement sensors emulation.
+ * Emulation channels are identified by unique names. For instance, sensor emulation
+ * is done via "sensors" channel, multi-touch emulation is done via "multi-touch"
+ * channel, etc.
+ * <p/>
+ * 2. Channels are connected to emulator via separate socket instance (though all
+ * of the connections share the same socket address).
+ * <p/>
+ * 3. Connection is initiated by the emulator side, while the service provides
+ * its side (a channel) that implement functionality and exchange protocol required
+ * by the requested type of emulation.
+ * <p/>
+ * Given that, the main responsibilities of this class are:
+ * <p/>
+ * 1. Bind to "android.sdk.controller" socket, listening to emulator connections.
+ * <p/>
+ * 2. Maintain a list of service-side channels registered by the application.
+ * <p/>
+ * 3. Bind emulator connection with service-side channel via port name, provided by
+ * the emulator.
+ * <p/>
+ * 4. Monitor connection state with the emulator, and automatically restore the
+ * connection once it is lost.
+ */
+public class Connection {
+ /** UNIX-domain name reserved for SDK controller. */
+ public static final String SDK_CONTROLLER_PORT = "android.sdk.controller";
+ /** Tag for logging messages. */
+ private static final String TAG = "SdkControllerConnection";
+ /** Controls debug logging */
+ private static final boolean DEBUG = false;
+
+ /** Server socket used to listen to emulator connections. */
+ private LocalServerSocket mServerSocket = null;
+ /** Service that has created this object. */
+ private ControllerService mService;
+ /**
+ * List of connected emulator sockets, pending for a channel to be registered.
+ * <p/>
+ * Emulator may connect to SDK controller before the app registers a channel
+ * for that connection. In this case (when app-side channel is not registered
+ * with this class) we will keep emulator connection in this list, pending
+ * for the app-side channel to register.
+ */
+ private List<Socket> mPendingSockets = new ArrayList<Socket>();
+ /**
+ * List of registered app-side channels.
+ * <p/>
+ * Channels that are kept in this list may be disconnected from (or pending
+ * connection with) the emulator, or they may be connected with the
+ * emulator.
+ */
+ private List<Channel> mChannels = new ArrayList<Channel>();
+
+ /**
+ * Constructs Connection instance.
+ */
+ public Connection(ControllerService service) {
+ mService = service;
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is constructed.");
+ }
+
+ /**
+ * Binds to the socket, and starts the listening thread.
+ */
+ public void connect() {
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is connecting...");
+ // Start connection listener.
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runIOLooper();
+ }
+ }, "SdkControllerConnectionIoLoop").start();
+ }
+
+ /**
+ * Stops the listener, and closes the socket.
+ *
+ * @return true if connection has been stopped in this call, or false if it
+ * has been already stopped when this method has been called.
+ */
+ public boolean disconnect() {
+ // This is the only place in this class where we will null the
+ // socket object. Since this method can be called concurrently from
+ // different threads, lets do this under the lock.
+ LocalServerSocket socket;
+ synchronized (this) {
+ socket = mServerSocket;
+ mServerSocket = null;
+ }
+ if (socket != null) {
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is stopping I/O looper...");
+ // Stop accepting new connections.
+ wakeIOLooper(socket);
+ try {
+ socket.close();
+ } catch (Exception e) {
+ }
+
+ // Close all the pending sockets, and clear pending socket list.
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is closing pending sockets...");
+ for (Socket pending_socket : mPendingSockets) {
+ pending_socket.close();
+ }
+ mPendingSockets.clear();
+
+ // Disconnect all the emualtors.
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnecting channels...");
+ for (Channel channel : mChannels) {
+ if (channel.disconnect()) {
+ channel.onEmulatorDisconnected();
+ }
+ }
+ if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnected.");
+ }
+ return socket != null;
+ }
+
+ /**
+ * Registers SDK controller channel.
+ *
+ * @param channel SDK controller emulator to register.
+ * @return true if channel has been registered successfully, or false if channel
+ * with the same name is already registered.
+ */
+ public boolean registerChannel(Channel channel) {
+ for (Channel check_channel : mChannels) {
+ if (check_channel.getChannelName().equals(channel.getChannelName())) {
+ Loge("Registering a duplicate Channel " + channel.getChannelName());
+ return false;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Registering Channel " + channel.getChannelName());
+ mChannels.add(channel);
+
+ // Lets see if there is a pending socket for this channel.
+ for (Socket pending_socket : mPendingSockets) {
+ if (pending_socket.getChannelName().equals(channel.getChannelName())) {
+ // Remove the socket from the pending list, and connect the registered channel with it.
+ if (DEBUG) Log.d(TAG, "Found pending Socket for registering Channel "
+ + channel.getChannelName());
+ mPendingSockets.remove(pending_socket);
+ channel.connect(pending_socket);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if at least one socket connection exists with channel.
+ *
+ * @return true if at least one socket connection exists with channel.
+ */
+ public boolean isEmulatorConnected() {
+ for (Channel channel : mChannels) {
+ if (channel.isConnected()) {
+ return true;
+ }
+ }
+ return !mPendingSockets.isEmpty();
+ }
+
+ /**
+ * Gets Channel instance for the given channel name.
+ *
+ * @param name Channel name to get Channel instance for.
+ * @return Channel instance for the given channel name, or NULL if no
+ * channel has been registered for that name.
+ */
+ public Channel getChannel(String name) {
+ for (Channel channel : mChannels) {
+ if (channel.getChannelName().equals(name)) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets connected emulator socket that is pending for service-side channel
+ * registration.
+ *
+ * @param name Channel name to lookup Socket for.
+ * @return Connected emulator socket that is pending for service-side channel
+ * registration, or null if no socket is pending for service-size
+ * channel registration.
+ */
+ private Socket getPendingSocket(String name) {
+ for (Socket socket : mPendingSockets) {
+ if (socket.getChannelName().equals(name)) {
+ return socket;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Wakes I/O looper waiting on connection with the emulator.
+ *
+ * @param socket Server socket waiting on connection.
+ */
+ private void wakeIOLooper(LocalServerSocket socket) {
+ // We wake the looper by connecting to the socket.
+ LocalSocket waker = new LocalSocket();
+ try {
+ waker.connect(socket.getLocalSocketAddress());
+ } catch (IOException e) {
+ Loge("Exception " + e + " in SdkControllerConnection while waking up the I/O looper.");
+ }
+ }
+
+ /**
+ * Loops on the local socket, handling emulator connection attempts.
+ */
+ private void runIOLooper() {
+ if (DEBUG) Log.d(TAG, "In SdkControllerConnection I/O looper.");
+ do {
+ try {
+ // Create non-blocking server socket that would listen for connections,
+ // and bind it to the given port on the local host.
+ mServerSocket = new LocalServerSocket(SDK_CONTROLLER_PORT);
+ LocalServerSocket socket = mServerSocket;
+ while (socket != null) {
+ final LocalSocket sk = socket.accept();
+ if (mServerSocket != null) {
+ onAccept(sk);
+ } else {
+ break;
+ }
+ socket = mServerSocket;
+ }
+ } catch (IOException e) {
+ Loge("Exception " + e + "SdkControllerConnection I/O looper.");
+ }
+ if (DEBUG) Log.d(TAG, "Exiting SdkControllerConnection I/O looper.");
+
+ // If we're exiting the internal loop for reasons other than an explicit
+ // disconnect request, we should reconnect again.
+ } while (disconnect());
+ }
+
+ /**
+ * Accepts new connection from the emulator.
+ *
+ * @param sock Connecting socket.
+ * @throws IOException
+ */
+ private void onAccept(LocalSocket sock) throws IOException {
+ final ByteBuffer handshake = ByteBuffer.allocate(ProtocolConstants.QUERY_HEADER_SIZE);
+
+ // By protocol, first byte received from newly connected emulator socket
+ // indicates host endianness.
+ Socket.receive(sock, handshake.array(), 1);
+ final ByteOrder endian = (handshake.getChar() == 0) ? ByteOrder.LITTLE_ENDIAN :
+ ByteOrder.BIG_ENDIAN;
+ handshake.order(endian);
+
+ // Right after that follows the handshake query header.
+ handshake.position(0);
+ Socket.receive(sock, handshake.array(), handshake.array().length);
+
+ // First int - signature
+ final int signature = handshake.getInt();
+ assert signature == ProtocolConstants.PACKET_SIGNATURE;
+ // Second int - total query size (including fixed query header)
+ final int remains = handshake.getInt() - ProtocolConstants.QUERY_HEADER_SIZE;
+ // After that - header type (which must be SDKCTL_PACKET_TYPE_QUERY)
+ final int msg_type = handshake.getInt();
+ assert msg_type == ProtocolConstants.PACKET_TYPE_QUERY;
+ // After that - query ID.
+ final int query_id = handshake.getInt();
+ // And finally, query type (which must be ProtocolConstants.QUERY_HANDSHAKE for
+ // handshake query)
+ final int query_type = handshake.getInt();
+ assert query_type == ProtocolConstants.QUERY_HANDSHAKE;
+ // Verify that received is a query.
+ if (msg_type != ProtocolConstants.PACKET_TYPE_QUERY) {
+ // Message type is not a query. Lets read and discard the remainder
+ // of the message.
+ if (remains > 0) {
+ Loge("Unexpected handshake message type: " + msg_type);
+ byte[] discard = new byte[remains];
+ Socket.receive(sock, discard, discard.length);
+ }
+ return;
+ }
+
+ // Receive query data.
+ final byte[] name_array = new byte[remains];
+ Socket.receive(sock, name_array, name_array.length);
+
+ // Prepare response header.
+ handshake.position(0);
+ handshake.putInt(ProtocolConstants.PACKET_SIGNATURE);
+ // Handshake reply is just one int.
+ handshake.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + 4);
+ handshake.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
+ handshake.putInt(query_id);
+
+ // Verify that received query is in deed a handshake query.
+ if (query_type != ProtocolConstants.QUERY_HANDSHAKE) {
+ // Query is not a handshake. Reply with failure.
+ Loge("Unexpected handshake query type: " + query_type);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_QUERY_UNKNOWN);
+ sock.getOutputStream().write(handshake.array());
+ return;
+ }
+
+ // Handshake query data consist of SDK controller channel name.
+ final String channel_name = new String(name_array);
+ if (DEBUG) Log.d(TAG, "Handshake received for channel " + channel_name);
+
+ // Respond to query depending on service-side channel availability
+ final Channel channel = getChannel(channel_name);
+ Socket sk = null;
+
+ if (channel != null) {
+ if (channel.isConnected()) {
+ // This is a duplicate connection.
+ Loge("Duplicate connection to a connected Channel " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
+ } else {
+ // Connecting to a registered channel.
+ if (DEBUG) Log.d(TAG, "Emulator is connected to a registered Channel " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_CONNECTED);
+ }
+ } else {
+ // Make sure that there are no other channel connections for this
+ // channel name.
+ if (getPendingSocket(channel_name) != null) {
+ // This is a duplicate.
+ Loge("Duplicate connection to a pending Socket " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
+ } else {
+ // Connecting to a channel that has not been registered yet.
+ if (DEBUG) Log.d(TAG, "Emulator is connected to a pending Socket " + channel_name);
+ handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_NOPORT);
+ sk = new Socket(sock, channel_name, endian);
+ mPendingSockets.add(sk);
+ }
+ }
+
+ // Send handshake reply.
+ sock.getOutputStream().write(handshake.array());
+
+ // If a disconnected channel for emulator connection has been found,
+ // connect it.
+ if (channel != null && !channel.isConnected()) {
+ if (DEBUG) Log.d(TAG, "Connecting Channel " + channel_name + " with emulator.");
+ sk = new Socket(sock, channel_name, endian);
+ channel.connect(sk);
+ }
+
+ mService.notifyStatusChanged();
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
deleted file mode 100755
index f7682f8..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
+++ /dev/null
@@ -1,968 +0,0 @@
-/*
- * 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.tools.sdkcontroller.lib;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.ClosedSelectorException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.nio.channels.spi.SelectorProvider;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.Vector;
-
-import android.util.Log;
-
-/**
- * Encapsulates a connection with the emulator. The connection is established
- * over a TCP port forwarding enabled with 'adb forward' command.
- * <p/>
- * Communication with the emulator is performed via two socket channels
- * connected to the forwarded TCP port. One channel is a query channel that is
- * intended solely for receiving queries from the emulator. Another channel is
- * an event channel that is intended for sending notification messages (events)
- * to the emulator.
- * <p/>
- * EmulatorConnection is considered to be "connected" when both channels are connected.
- * EmulatorConnection is considered to be "disconnected" when connection with any of the
- * channels is lost.
- * <p/>
- * Instance of this class is operational only for a single connection with the
- * emulator. Once connection is established and then lost, a new instance of
- * this class must be created to establish new connection.
- * <p/>
- * Note that connection with the device over TCP port forwarding is extremely
- * fragile at the moment. For whatever reason the connection is even more
- * fragile if device uses asynchronous sockets (based on java.nio API). So, to
- * address this issue EmulatorConnection class implements two types of connections. One is
- * using synchronous sockets, and another is using asynchronous sockets. The
- * type of connection is selected when EmulatorConnection instance is created (see
- * comments to EmulatorConnection's constructor).
- * <p/>
- * According to the exchange protocol with the emulator, queries, responses to
- * the queries, and notification messages are all zero-terminated strings.
- */
-public class EmulatorConnection {
- /** Defines connection types supported by the EmulatorConnection class. */
- public enum EmulatorConnectionType {
- /** Use asynchronous connection (based on java.nio API). */
- ASYNC_CONNECTION,
- /** Use synchronous connection (based on synchronous Socket objects). */
- SYNC_CONNECTION,
- }
-
- /** TCP port reserved for the sensors emulation. */
- public static final int SENSORS_PORT = 1968;
- /** TCP port reserved for the multitouch emulation. */
- public static final int MULTITOUCH_PORT = 1969;
- /** Tag for logging messages. */
- private static final String TAG = "EmulatorConnection";
- /** EmulatorConnection events listener. */
- private EmulatorListener mListener;
- /** I/O selector (looper). */
- private Selector mSelector;
- /** Server socket channel. */
- private ServerSocketChannel mServerSocket;
- /** Query channel. */
- private EmulatorChannel mQueryChannel;
- /** Event channel. */
- private EmulatorChannel mEventChannel;
- /** Selector for the connection type. */
- private EmulatorConnectionType mConnectionType;
- /** Connection status */
- private boolean mIsConnected = false;
- /** Disconnection status */
- private boolean mIsDisconnected = false;
- /** Exit I/O loop flag. */
- private boolean mExitIoLoop = false;
- /** Disconnect flag. */
- private boolean mDisconnect = false;
-
- /***************************************************************************
- * EmulatorChannel - Base class for sync / async channels.
- **************************************************************************/
-
- /**
- * Encapsulates a base class for synchronous and asynchronous communication
- * channels.
- */
- private abstract class EmulatorChannel {
- /** Identifier for a query channel type. */
- private static final String QUERY_CHANNEL = "query";
- /** Identifier for an event channel type. */
- private static final String EVENT_CHANNEL = "event";
- /** BLOB query string. */
- private static final String BLOBL_QUERY = "$BLOB";
-
- /***********************************************************************
- * Abstract API
- **********************************************************************/
-
- /**
- * Sends a message via this channel.
- *
- * @param msg Zero-terminated message string to send.
- */
- public abstract void sendMessage(String msg) throws IOException;
-
- /**
- * Closes this channel.
- */
- abstract public void closeChannel() throws IOException;
-
- /***********************************************************************
- * Public API
- **********************************************************************/
-
- /**
- * Constructs EmulatorChannel instance.
- */
- public EmulatorChannel() {
- }
-
- /**
- * Handles a query received in this channel.
- *
- * @param socket A socket through which the query has been received.
- * @param query_str Query received from this channel. All queries are
- * formatted as such: <query>:<query parameters> where -
- * <query> Is a query name that identifies the query, and -
- * <query parameters> represent parameters for the query.
- * Query name and query parameters are separated with a ':'
- * character.
- */
- public void onQueryReceived(Socket socket, String query_str) throws IOException {
- String query, query_param, response;
-
- // Lets see if query has parameters.
- int sep = query_str.indexOf(':');
- if (sep == -1) {
- // Query has no parameters.
- query = query_str;
- query_param = "";
- } else {
- // Separate query name from its parameters.
- query = query_str.substring(0, sep);
- // Make sure that substring after the ':' does contain
- // something, otherwise the query is paramless.
- query_param = (sep < (query_str.length() - 1)) ? query_str.substring(sep + 1) : "";
- }
-
- // Handle the query, obtain response string, and reply it back to
- // the emulator. Note that there is one special query: $BLOB, that
- // requires reading of a byte array of data first. The size of the
- // array is defined by the query parameter.
- if (query.compareTo(BLOBL_QUERY) == 0) {
- // This is the BLOB query. It must have a parameter which
- // contains byte size of the blob.
- final int array_size = Integer.parseInt(query_param);
- if (array_size > 0) {
- // Read data from the query's socket.
- byte[] array = new byte[array_size];
- final int transferred = readSocketArray(socket, array);
- if (transferred == array_size) {
- // Handle blob query.
- response = onBlobQuery(array);
- } else {
- response = "ko:Transfer failure\0";
- }
- } else {
- response = "ko:Invalid parameter\0";
- }
- } else {
- response = onQuery(query, query_param);
- if (response.length() == 0 || response.charAt(0) == '\0') {
- Logw("No response to the query " + query_str);
- }
- }
-
- if (response.length() != 0) {
- if (response.charAt(response.length() - 1) != '\0') {
- Logw("Response '" + response + "' to query '" + query
- + "' does not contain zero-terminator.");
- }
- sendMessage(response);
- }
- }
- } // EmulatorChannel
-
- /***************************************************************************
- * EmulatorSyncChannel - Implements a synchronous channel.
- **************************************************************************/
-
- /**
- * Encapsulates a synchronous communication channel with the emulator.
- */
- private class EmulatorSyncChannel extends EmulatorChannel {
- /** Communication socket. */
- private Socket mSocket;
-
- /**
- * Constructs EmulatorSyncChannel instance.
- *
- * @param socket Connected ('accept'ed) communication socket.
- */
- public EmulatorSyncChannel(Socket socket) {
- mSocket = socket;
- // Start the reader thread.
- new Thread(new Runnable() {
- @Override
- public void run() {
- theReader();
- }
- }, "EmuSyncChannel").start();
- }
-
- /***********************************************************************
- * Abstract API implementation
- **********************************************************************/
-
- /**
- * Sends a message via this channel.
- *
- * @param msg Zero-terminated message string to send.
- */
- @Override
- public void sendMessage(String msg) throws IOException {
- if (msg.charAt(msg.length() - 1) != '\0') {
- Logw("Missing zero-terminator in message '" + msg + "'");
- }
- mSocket.getOutputStream().write(msg.getBytes());
- }
-
- /**
- * Closes this channel.
- */
- @Override
- public void closeChannel() throws IOException {
- mSocket.close();
- }
-
- /***********************************************************************
- * EmulatorSyncChannel implementation
- **********************************************************************/
-
- /**
- * The reader thread: loops reading and dispatching queries.
- */
- private void theReader() {
- try {
- for (;;) {
- String query = readSocketString(mSocket);
- onQueryReceived(mSocket, query);
- }
- } catch (IOException e) {
- onLostConnection();
- }
- }
- } // EmulatorSyncChannel
-
- /***************************************************************************
- * EmulatorAsyncChannel - Implements an asynchronous channel.
- **************************************************************************/
-
- /**
- * Encapsulates an asynchronous communication channel with the emulator.
- */
- private class EmulatorAsyncChannel extends EmulatorChannel {
- /** Communication socket channel. */
- private SocketChannel mChannel;
- /** I/O selection key for this channel. */
- private SelectionKey mSelectionKey;
- /** Accumulator for the query string received in this channel. */
- private String mQuery = "";
- /**
- * Preallocated character reader that is used when data is read from
- * this channel. See 'onRead' method for more details.
- */
- private ByteBuffer mIn = ByteBuffer.allocate(1);
- /**
- * Currently sent notification message(s). See 'sendMessage', and
- * 'onWrite' methods for more details.
- */
- private ByteBuffer mOut;
- /**
- * Array of pending notification messages. See 'sendMessage', and
- * 'onWrite' methods for more details.
- */
- private Vector<String> mNotifications = new Vector<String>();
-
- /**
- * Constructs EmulatorAsyncChannel instance.
- *
- * @param channel Accepted socket channel to use for communication.
- * @throws IOException
- */
- private EmulatorAsyncChannel(SocketChannel channel) throws IOException {
- // Mark character reader at the beginning, so we can reset it after
- // next read character has been pulled out from the buffer.
- mIn.mark();
-
- // Configure communication channel as non-blocking, and register
- // it with the I/O selector for reading.
- mChannel = channel;
- mChannel.configureBlocking(false);
- mSelectionKey = mChannel.register(mSelector, SelectionKey.OP_READ, this);
- // Start receiving read I/O.
- mSelectionKey.selector().wakeup();
- }
-
- /***********************************************************************
- * Abstract API implementation
- **********************************************************************/
-
- /**
- * Sends a message via this channel.
- *
- * @param msg Zero-terminated message string to send.
- */
- @Override
- public void sendMessage(String msg) throws IOException {
- if (msg.charAt(msg.length() - 1) != '\0') {
- Logw("Missing zero-terminator in message '" + msg + "'");
- }
- synchronized (this) {
- if (mOut != null) {
- // Channel is busy with writing another message.
- // Queue this one. It will be picked up later when current
- // write operation is completed.
- mNotifications.add(msg);
- return;
- }
-
- // No other messages are in progress. Send this one outside of
- // the lock.
- mOut = ByteBuffer.wrap(msg.getBytes());
- }
- mChannel.write(mOut);
-
- // Lets see if we were able to send the entire message.
- if (mOut.hasRemaining()) {
- // Write didn't complete. Schedule write I/O callback to
- // pick up from where this write has left.
- enableWrite();
- return;
- }
-
- // Entire message has been sent. Lets see if other messages were
- // queued while we were busy sending this one.
- for (;;) {
- synchronized (this) {
- // Dequeue message that was yielding to this write.
- if (!dequeueMessage()) {
- // Writing is over...
- disableWrite();
- mOut = null;
- return;
- }
- }
-
- // Send queued message.
- mChannel.write(mOut);
-
- // Lets see if we were able to send the entire message.
- if (mOut.hasRemaining()) {
- // Write didn't complete. Schedule write I/O callback to
- // pick up from where this write has left.
- enableWrite();
- return;
- }
- }
- }
-
- /**
- * Closes this channel.
- */
- @Override
- public void closeChannel() throws IOException {
- mSelectionKey.cancel();
- synchronized (this) {
- mNotifications.clear();
- }
- mChannel.close();
- }
-
- /***********************************************************************
- * EmulatorAsyncChannel implementation
- **********************************************************************/
-
- /**
- * Reads data from the channel. This method is invoked from the I/O loop
- * when data is available for reading on this channel. When reading from
- * a channel we read character-by-character, building the query string
- * until zero-terminator is read. When zero-terminator is read, we
- * handle the query, and start building the new query string.
- *
- * @throws IOException
- */
- private void onRead() throws IOException, ClosedChannelException {
- int count = mChannel.read(mIn);
- Logv("onRead: " + count);
- while (count == 1) {
- final char c = (char) mIn.array()[0];
- mIn.reset();
- if (c == '\0') {
- // Zero-terminator is read. Process the query, and reset
- // the query string.
- onQueryReceived(mChannel.socket(), mQuery);
- mQuery = "";
- } else {
- // Continue building the query string.
- mQuery += c;
- }
- count = mChannel.read(mIn);
- }
-
- if (count == -1) {
- // Channel got disconnected.
- throw new ClosedChannelException();
- } else {
- // "Don't block" in effect. Will get back to reading as soon as
- // read I/O is available.
- assert (count == 0);
- }
- }
-
- /**
- * Writes data to the channel. This method is ivnoked from the I/O loop
- * when data is available for writing on this channel.
- *
- * @throws IOException
- */
- private void onWrite() throws IOException {
- if (mOut != null && mOut.hasRemaining()) {
- // Continue writing to the channel.
- mChannel.write(mOut);
- if (mOut.hasRemaining()) {
- // Write is still incomplete. Come back to it when write I/O
- // becomes available.
- return;
- }
- }
-
- // We're done with the current message. Lets see if we've
- // accumulated some more while this write was in progress.
- synchronized (this) {
- // Dequeue next message into mOut.
- if (!dequeueMessage()) {
- // Nothing left to write.
- disableWrite();
- mOut = null;
- return;
- }
- // We don't really want to run a big loop here, flushing the
- // message queue. The reason is that we're inside the I/O loop,
- // so we don't want to block others for long. So, we will
- // continue with queue flushing next time we're picked up by
- // write I/O event.
- }
- }
-
- /**
- * Dequeues messages that were yielding to the write in progress.
- * Messages will be dequeued directly to the mOut, so it's ready to be
- * sent when this method returns. NOTE: This method must be called from
- * within synchronized(this).
- *
- * @return true if messages were dequeued, or false if message queue was
- * empty.
- */
- private boolean dequeueMessage() {
- // It's tempting to dequeue all messages here, but in practice it's
- // less performant than dequeuing just one.
- if (!mNotifications.isEmpty()) {
- mOut = ByteBuffer.wrap(mNotifications.remove(0).getBytes());
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Enables write I/O callbacks.
- */
- private void enableWrite() {
- mSelectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
- // Looks like we must wake up the selector. Otherwise it's not going
- // to immediately pick up on the change that we just made.
- mSelectionKey.selector().wakeup();
- }
-
- /**
- * Disables write I/O callbacks.
- */
- private void disableWrite() {
- mSelectionKey.interestOps(SelectionKey.OP_READ);
- }
- } // EmulatorChannel
-
- /***************************************************************************
- * EmulatorConnection public API
- **************************************************************************/
-
- /**
- * Constructs EmulatorConnection instance.
- * Caller must call {@link #connect(int, EmulatorConnectionType)} afterwards.
- *
- * @param listener EmulatorConnection event listener. Must not be null.
- */
- public EmulatorConnection(EmulatorListener listener) {
- mListener = listener;
- }
-
- /**
- * Connects the EmulatorConnection instance.
- * <p/>
- * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
- * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
- *
- * @param port TCP port where emulator connects.
- * @param ctype Defines connection type to use (sync / async). See comments
- * to EmulatorConnection class for more info.
- * @return This object for chaining calls.
- */
- public EmulatorConnection connect(int port, EmulatorConnectionType ctype) {
- constructEmulator(port, ctype);
- return this;
- }
-
-
- /**
- * Disconnects the emulator.
- */
- public void disconnect() {
- mDisconnect = true;
- mSelector.wakeup();
- }
-
- /**
- * Constructs EmulatorConnection instance.
- * <p/>
- * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
- * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
- * <p/>
- * On error or success, this calls
- * {@link EmulatorListener#onEmulatorBindResult(boolean, Exception)} to indicate whether
- * the socket was properly bound.
- * The IO loop will start after the method reported a successful bind.
- *
- * @param port TCP port where emulator connects.
- * @param ctype Defines connection type to use (sync / async). See comments
- * to EmulatorConnection class for more info.
- */
- private void constructEmulator(final int port, EmulatorConnectionType ctype) {
-
- try {
- mConnectionType = ctype;
- // Create I/O looper.
- mSelector = SelectorProvider.provider().openSelector();
-
- // Create non-blocking server socket that would listen for connections,
- // and bind it to the given port on the local host.
- mServerSocket = ServerSocketChannel.open();
- mServerSocket.configureBlocking(false);
- InetAddress local = InetAddress.getLocalHost();
- final InetSocketAddress address = new InetSocketAddress(local, port);
- mServerSocket.socket().bind(address);
-
- // Register 'accept' I/O on the server socket.
- mServerSocket.register(mSelector, SelectionKey.OP_ACCEPT);
- } catch (IOException e) {
- mListener.onEmulatorBindResult(false, e);
- return;
- }
-
- mListener.onEmulatorBindResult(true, null);
- Logv("EmulatorConnection listener is created for port " + port);
-
- // Start I/O looper and dispatcher.
- new Thread(new Runnable() {
- @Override
- public void run() {
- runIOLooper();
- }
- }, "EmuCnxIoLoop").start();
- }
-
- /**
- * Sends a notification message to the emulator via 'event' channel.
- * <p/>
- * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
- * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
- *
- * @param msg
- */
- public void sendNotification(String msg) {
- if (mIsConnected) {
- try {
- mEventChannel.sendMessage(msg);
- } catch (IOException e) {
- onLostConnection();
- }
- } else {
- Logw("Attempt to send '" + msg + "' to a disconnected EmulatorConnection");
- }
- }
-
- /**
- * Sets or removes a listener to the events generated by this emulator
- * instance.
- *
- * @param listener Listener to set. Passing null with this parameter will
- * remove the current listener (if there was one).
- */
- public void setEmulatorListener(EmulatorListener listener) {
- synchronized (this) {
- mListener = listener;
- }
- // Make sure that new listener knows the connection status.
- if (mListener != null) {
- if (mIsConnected) {
- mListener.onEmulatorConnected();
- } else if (mIsDisconnected) {
- mListener.onEmulatorDisconnected();
- }
- }
- }
-
- /***************************************************************************
- * EmulatorConnection events
- **************************************************************************/
-
- /**
- * Called when emulator is connected. NOTE: This method is called from the
- * I/O loop, so all communication with the emulator will be "on hold" until
- * this method returns.
- */
- private void onConnected() {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- listener.onEmulatorConnected();
- }
- }
-
- /**
- * Called when emulator is disconnected. NOTE: This method could be called
- * from the I/O loop, in which case all communication with the emulator will
- * be "on hold" until this method returns.
- */
- private void onDisconnected() {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- listener.onEmulatorDisconnected();
- }
- }
-
- /**
- * Called when a query is received from the emulator. NOTE: This method
- * could be called from the I/O loop, in which case all communication with
- * the emulator will be "on hold" until this method returns.
- *
- * @param query Name of the query received from the emulator.
- * @param param Query parameters.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
- */
- private String onQuery(String query, String param) {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- return listener.onEmulatorQuery(query, param);
- } else {
- return "ko:Service is detached.\0";
- }
- }
-
- /**
- * Called when a BLOB query is received from the emulator. NOTE: This method
- * could be called from the I/O loop, in which case all communication with
- * the emulator will be "on hold" until this method returns.
- *
- * @param array Array containing blob data.
- * @return Zero-terminated reply string. String must be formatted as such:
- * "ok|ko[:reply data]"
- */
- private String onBlobQuery(byte[] array) {
- EmulatorListener listener;
- synchronized (this) {
- listener = mListener;
- }
- if (listener != null) {
- return listener.onEmulatorBlobQuery(array);
- } else {
- return "ko:Service is detached.\0";
- }
- }
-
- /***************************************************************************
- * EmulatorConnection implementation
- **************************************************************************/
-
- /**
- * Loops on the selector, handling and dispatching I/O events.
- */
- private void runIOLooper() {
- try {
- Logv("Waiting on EmulatorConnection to connect...");
- // Check mExitIoLoop before calling 'select', and after in order to
- // detect condition when mSelector has been waken up to exit the
- // I/O loop.
- while (!mExitIoLoop && !mDisconnect &&
- mSelector.select() >= 0 &&
- !mExitIoLoop && !mDisconnect) {
- Set<SelectionKey> readyKeys = mSelector.selectedKeys();
- Iterator<SelectionKey> i = readyKeys.iterator();
- while (i.hasNext()) {
- SelectionKey sk = i.next();
- i.remove();
- if (sk.isAcceptable()) {
- final int ready = sk.readyOps();
- if ((ready & SelectionKey.OP_ACCEPT) != 0) {
- // Accept new connection.
- onAccept(((ServerSocketChannel) sk.channel()).accept());
- }
- } else {
- // Read / write events are expected only on a 'query',
- // or 'event' asynchronous channels.
- EmulatorAsyncChannel esc = (EmulatorAsyncChannel) sk.attachment();
- if (esc != null) {
- final int ready = sk.readyOps();
- if ((ready & SelectionKey.OP_READ) != 0) {
- // Read data.
- esc.onRead();
- }
- if ((ready & SelectionKey.OP_WRITE) != 0) {
- // Write data.
- esc.onWrite();
- }
- } else {
- Loge("No emulator channel found in selection key.");
- }
- }
- }
- }
- } catch (ClosedSelectorException e) {
- } catch (IOException e) {
- }
-
- // Destroy connection on any I/O failure.
- if (!mExitIoLoop) {
- onLostConnection();
- }
- }
-
- /**
- * Accepts new connection from the emulator.
- *
- * @param channel Connecting socket channel.
- * @throws IOException
- */
- private void onAccept(SocketChannel channel) throws IOException {
- // Make sure we're not connected yet.
- if (mEventChannel != null && mQueryChannel != null) {
- // We don't accept any more connections after both channels were
- // connected.
- Loge("EmulatorConnection is connecting to the already connected instance.");
- channel.close();
- return;
- }
-
- // According to the protocol, each channel identifies itself as a query
- // or event channel, sending a "cmd", or "event" message right after
- // the connection.
- Socket socket = channel.socket();
- String socket_type = readSocketString(socket);
- if (socket_type.contentEquals(EmulatorChannel.QUERY_CHANNEL)) {
- if (mQueryChannel == null) {
- // TODO: Find better way to do that!
- socket.getOutputStream().write("ok\0".getBytes());
- if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {
- mQueryChannel = new EmulatorAsyncChannel(channel);
- Logv("Asynchronous query channel is registered.");
- } else {
- mQueryChannel = new EmulatorSyncChannel(channel.socket());
- Logv("Synchronous query channel is registered.");
- }
- } else {
- // TODO: Find better way to do that!
- Loge("Duplicate query channel.");
- socket.getOutputStream().write("ko:Duplicate\0".getBytes());
- channel.close();
- return;
- }
- } else if (socket_type.contentEquals(EmulatorChannel.EVENT_CHANNEL)) {
- if (mEventChannel == null) {
- // TODO: Find better way to do that!
- socket.getOutputStream().write("ok\0".getBytes());
- if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {
- mEventChannel = new EmulatorAsyncChannel(channel);
- Logv("Asynchronous event channel is registered.");
- } else {
- mEventChannel = new EmulatorSyncChannel(channel.socket());
- Logv("Synchronous event channel is registered.");
- }
- } else {
- Loge("Duplicate event channel.");
- socket.getOutputStream().write("ko:Duplicate\0".getBytes());
- channel.close();
- return;
- }
- } else {
- Loge("Unknown channel is connecting: " + socket_type);
- socket.getOutputStream().write("ko:Unknown channel type\0".getBytes());
- channel.close();
- return;
- }
-
- // Lets see if connection is complete...
- if (mEventChannel != null && mQueryChannel != null) {
- // When both, query and event channels are connected, the emulator
- // is considered to be connected.
- Logv("... EmulatorConnection is connected.");
- mIsConnected = true;
- onConnected();
- }
- }
-
- /**
- * Called when connection to any of the channels has been lost.
- */
- private void onLostConnection() {
- // Since we're multithreaded, there can be multiple "bangs" from those
- // threads. We should only handle the first one.
- boolean first_time = false;
- synchronized (this) {
- first_time = mIsConnected;
- mIsConnected = false;
- mIsDisconnected = true;
- }
- if (first_time) {
- Logw("Connection with the emulator is lost!");
- // Close all channels, exit the I/O loop, and close the selector.
- try {
- if (mEventChannel != null) {
- mEventChannel.closeChannel();
- }
- if (mQueryChannel != null) {
- mQueryChannel.closeChannel();
- }
- if (mServerSocket != null) {
- mServerSocket.close();
- }
- if (mSelector != null) {
- mExitIoLoop = true;
- mSelector.wakeup();
- mSelector.close();
- }
- } catch (IOException e) {
- Loge("onLostConnection exception: " + e.getMessage());
- }
-
- // Notify the app about lost connection.
- onDisconnected();
- }
- }
-
- /**
- * Reads zero-terminated string from a synchronous socket.
- *
- * @param socket Socket to read string from. Must be a synchronous socket.
- * @return String read from the socket.
- * @throws IOException
- */
- private static String readSocketString(Socket socket) throws IOException {
- String str = "";
-
- // Current characted received from the input stream.
- int current_byte = 0;
-
- // With port forwarding there is no reliable way how to detect
- // socket disconnection, other than checking on the input stream
- // to die ("end of stream" condition). That condition is reported
- // when input stream's read() method returns -1.
- while (socket.isConnected() && current_byte != -1) {
- // Character by character read the input stream, and accumulate
- // read characters in the command string. The end of the command
- // is indicated with zero character.
- current_byte = socket.getInputStream().read();
- if (current_byte != -1) {
- if (current_byte == 0) {
- // String is completed.
- return str;
- } else {
- // Append read character to the string.
- str += (char) current_byte;
- }
- }
- }
-
- // Got disconnected!
- throw new ClosedChannelException();
- }
-
- /**
- * Reads a block of data from a socket.
- *
- * @param socket Socket to read data from. Must be a synchronous socket.
- * @param array Array where to read data.
- * @return Number of bytes read from the socket, or -1 on an error.
- * @throws IOException
- */
- private static int readSocketArray(Socket socket, byte[] array) throws IOException {
- int in = 0;
- while (in < array.length) {
- final int ret = socket.getInputStream().read(array, in, array.length - in);
- if (ret == -1) {
- // Got disconnected!
- throw new ClosedChannelException();
- }
- in += ret;
- }
- return in;
- }
-
- /***************************************************************************
- * Logging wrappers
- **************************************************************************/
-
- private void Loge(String log) {
- Log.e(TAG, log);
- }
-
- private void Logw(String log) {
- Log.w(TAG, log);
- }
-
- private void Logv(String log) {
- Log.v(TAG, log);
- }
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java
deleted file mode 100644
index 4d2a19f..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.tools.sdkcontroller.lib;
-
-
-/**
- * Encapsulates a listener to emulator events. An object implementing this
- * interface must be registered with the EmulatorConnection instance via
- * setEmulatorListener method of the EmulatorConnection class.
- */
-public interface EmulatorListener {
-
- /**
- * Called as a side effect of constructing a new {@link EmulatorConnection} to
- * indicate whether the when emulator is bound with its communication socket.
- *
- * @param success True if the socket bind was successful.
- * False when the socket bind failed.
- * @param e Any exception thrown whilst trying to bind to the communication socket.
- * Null if there's no exception (typically when {@code success==true}).
- */
- public void onEmulatorBindResult(boolean success, Exception e);
-
- /**
- * Called when emulator is connected. NOTE: This method is called from the
- * I/O loop, so all communication with the emulator will be "on hold" until
- * this method returns.
- */
- public void onEmulatorConnected();
-
- /**
- * Called when emulator is disconnected. NOTE: This method could be called
- * from the I/O loop, in which case all communication with the emulator will
- * be "on hold" until this method returns.
- */
- public void onEmulatorDisconnected();
-
- /**
- * Called when a query is received from the emulator. NOTE: This method is
- * called from the I/O loop, so all communication with the emulator will be
- * "on hold" until this method returns.
- *
- * @param query Name of the query received from the emulator.
- * @param param Query parameters.
- * @return Zero-terminated reply string. If not an empty string is returned,
- * it must be formatted as such: "ok|ko[:reply data]"
- */
- public String onEmulatorQuery(String query, String param);
-
- /**
- * Called when a BLOB query is received from the emulator. NOTE: This method
- * is called from the I/O loop, so all communication with the emulator will
- * be "on hold" until this method returns.
- *
- * @param array contains BLOB data for the query.
- * @return Zero-terminated reply string. If not an empty string is returned,
- * it must be formatted as such: "ok|ko[:reply data]"
- */
- public String onEmulatorBlobQuery(byte[] array);
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
new file mode 100644
index 0000000..b4c6593
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
@@ -0,0 +1,142 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.tools.sdkcontroller.lib;
+
+/**
+ * Contains declarations of constants that are tied to emulator implementation.
+ * These constants can be changed only simultaneously in both places.
+ */
+public final class ProtocolConstants {
+ /*
+ * Constants related to data transfer.
+ */
+
+ /** Signature of a packet sent via SDK controller socket ('SDKC') */
+ public static final int PACKET_SIGNATURE = 0x53444B43;
+
+ /*
+ * Header sizes for packets sent / received by SDK controller emulator.
+ */
+
+ /**
+ * 12 bytes (3 ints) for the packet header:
+ * <p/>
+ * - Signature.
+ * <p/>
+ * - Total packet size.
+ * <p/>
+ * - Packet type.
+ */
+ public static final int PACKET_HEADER_SIZE = 12;
+ /**
+ * 16 bytes (4 ints) for the message header:
+ * <p/>
+ * - Common packet header.
+ * <p/>
+ * - Message type.
+ */
+ public static final int MESSAGE_HEADER_SIZE = 16;
+ /**
+ * 20 bytes (5 ints) for the query header:
+ * <p/>
+ * - Common packet header.
+ * <p/>
+ * - Query ID.
+ * <p/>
+ * - Query type.
+ */
+ public static final int QUERY_HEADER_SIZE = 20;
+ /**
+ * 16 bytes (4 ints) for the query response:
+ * <p/>
+ * - Common packet header.
+ * <p/>
+ * - Query ID.
+ */
+ public static final int QUERY_RESP_HEADER_SIZE = 16;
+
+ /*
+ * Types of packets transferred via SDK Controller channel.
+ */
+
+ /** Packet is a message. */
+ public static final int PACKET_TYPE_MESSAGE = 1;
+ /** Packet is a query. */
+ public static final int PACKET_TYPE_QUERY = 2;
+ /** Packet is a response to a query. */
+ public static final int PACKET_TYPE_QUERY_RESPONSE = 3;
+
+ /*
+ * Constants related to handshake protocol between the emulator and a channel.
+ */
+
+ /**
+ * Query type for a special "handshake" query.
+ * <p/>
+ * When emulator connects to SDK controller, the first thing that goes
+ * through the socket is a special "handshake" query that delivers channel name
+ * to the service.
+ */
+ public static final int QUERY_HANDSHAKE = -1;
+ /**
+ * Handshake query response on condition that service-side channel is available
+ * (registered).
+ */
+ public static final int HANDSHAKE_RESP_CONNECTED = 0;
+ /**
+ * Handshake query response on condition that service-side channel is not
+ * available (not registered).
+ */
+ public static final int HANDSHAKE_RESP_NOPORT = 1;
+ /**
+ * Handshake query response on condition that there is already an existing
+ * emulator connection for this channel. Emulator should stop connection
+ * attempts in this case.
+ */
+ public static final int HANDSHAKE_RESP_DUP = -1;
+ /** Response to an unknown handshake query type. */
+ public static final int HANDSHAKE_RESP_QUERY_UNKNOWN = -2;
+
+ /*
+ * Constants related to multi-touch emulation.
+ */
+
+ /** Received frame is JPEG image. */
+ public static final int MT_FRAME_JPEG = 1;
+ /** Received frame is RGB565 bitmap. */
+ public static final int MT_FRAME_RGB565 = 2;
+ /** Received frame is RGB888 bitmap. */
+ public static final int MT_FRAME_RGB888 = 3;
+
+ /** Pointer(s) moved. */
+ public static final int MT_MOVE = 1;
+ /** First pointer down message. */
+ public static final int MT_FISRT_DOWN = 2;
+ /** Last pointer up message. */
+ public static final int MT_LAST_UP = 3;
+ /** Pointer down message. */
+ public static final int MT_POINTER_DOWN = 4;
+ /** Pointer up message. */
+ public static final int MT_POINTER_UP = 5;
+ /** Sends framebuffer update. */
+ public static final int MT_FB_UPDATE = 6;
+ /** Size of an event entry in the touch event message to the emulator. */
+ public static final int MT_EVENT_ENTRY_SIZE = 16;
+
+ /*
+ * Constants related to sensor emulation.
+ */
+
+ /** Query type for a query that should return the list of available sensors. */
+ public static final int SENSORS_QUERY_LIST = 1;
+ /** Message that starts sensor emulation. */
+ public static final int SENSORS_START = 1;
+ /** Message that stops sensor emulation. */
+ public static final int SENSORS_STOP = 2;
+ /** Message that enables emulation of a particular sensor. */
+ public static final int SENSORS_ENABLE = 3;
+ /** Message that disables emulation of a particular sensor. */
+ public static final int SENSORS_DISABLE = 4;
+ /** Message that delivers sensor events to emulator. */
+ public static final int SENSORS_SENSOR_EVENT = 5;
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
new file mode 100644
index 0000000..08e6b28
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 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.tools.sdkcontroller.lib;
+
+import android.net.LocalSocket;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+
+/**
+ * Encapsulates a connection with the emulator over a UNIX-domain socket.
+ */
+public class Socket {
+ /** UNIX-domain socket connected with the emulator. */
+ private LocalSocket mSocket = null;
+ /** Channel name for the connection established via this socket. */
+ private String mChannelName;
+ /** Endianness of data transferred in this connection. */
+ private ByteOrder mEndian;
+
+ /** Tag for message logging. */
+ private static final String TAG = "SdkControllerSocket";
+ /** Controls debug log. */
+ private static boolean DEBUG = false;
+
+ /**
+ * Constructs Socket instance.
+ *
+ * @param socket Socket connection with the emulator.
+ * @param name Channel port name for this connection.
+ * @param endian Endianness of data transferred in this connection.
+ */
+ public Socket(LocalSocket socket, String name, ByteOrder endian) {
+ mSocket = socket;
+ mChannelName = name;
+ mEndian = endian;
+ if (DEBUG) Log.d(TAG, "Socket is constructed for " + mChannelName);
+ }
+
+ /**
+ * Gets connection status of this socket.
+ *
+ * @return true if socket is connected, or false if socket is not connected.
+ */
+ public boolean isConnected() {
+ return mSocket != null;
+ }
+
+ /**
+ * Gets channel name for this socket.
+ *
+ * @return Channel name for this socket.
+ */
+ public String getChannelName() {
+ return mChannelName;
+ }
+
+ /**
+ * Gets endianness of data transferred via this socket.
+ *
+ * @return Endianness of data transferred via this socket.
+ */
+ public ByteOrder getEndian() {
+ return mEndian;
+ }
+
+ /**
+ * Sends data to the socket.
+ *
+ * @param data Data to send. Data size is defined by the length of the
+ * array.
+ * @throws IOException
+ */
+ public void send(byte[] data) throws IOException {
+ // Use local copy of the socket, ensuring it's not going to NULL while
+ // we're working with it. If it gets closed, while we're in the middle
+ // of data transfer - it's OK, since it will produce an exception, and
+ // the caller will gracefully handle it.
+ //
+ // Same technique is used everywhere in this class where mSocket member
+ // is touched.
+ LocalSocket socket = mSocket;
+ if (socket == null) {
+ Logw("'send' request on closed Socket " + mChannelName);
+ throw new ClosedChannelException();
+ }
+ socket.getOutputStream().write(data);
+ }
+
+ /**
+ * Sends data to the socket.
+ *
+ * @param data Data to send.
+ * @param offset The start position in data from where to get bytes.
+ * @param len The number of bytes from data to write to this socket.
+ * @throws IOException
+ */
+ public void send(byte[] data, int offset, int len) throws IOException {
+ LocalSocket socket = mSocket;
+ if (socket == null) {
+ Logw("'send' request on closed Socket " + mChannelName);
+ throw new ClosedChannelException();
+ }
+ socket.getOutputStream().write(data, offset, len);
+ }
+
+ /**
+ * Receives data from the socket.
+ *
+ * @param socket Socket from where to receive data.
+ * @param data Array where to save received data.
+ * @param len Number of bytes to receive.
+ * @throws IOException
+ */
+ public static void receive(LocalSocket socket, byte[] data, int len) throws IOException {
+ final InputStream is = socket.getInputStream();
+ int received = 0;
+ while (received != len) {
+ final int chunk = is.read(data, received, len - received);
+ if (chunk < 0) {
+ throw new IOException(
+ "I/O failure while receiving SDK controller data from socket.");
+ }
+ received += chunk;
+ }
+ }
+
+ /**
+ * Receives data from the socket.
+ *
+ * @param data Array where to save received data.
+ * @param len Number of bytes to receive.
+ * @throws IOException
+ */
+ public void receive(byte[] data, int len) throws IOException {
+ LocalSocket socket = mSocket;
+ if (socket == null) {
+ Logw("'receive' request on closed Socket " + mChannelName);
+ throw new ClosedChannelException();
+ }
+ receive(socket, data, len);
+ }
+
+ /**
+ * Receives data from the socket.
+ *
+ * @param data Array where to save received data. Data size is defined by
+ * the size of the array.
+ * @throws IOException
+ */
+ public void receive(byte[] data) throws IOException {
+ receive(data, data.length);
+ }
+
+ /**
+ * Closes the socket.
+ *
+ * @return true if socket has been closed in this call, or false if it had
+ * been already closed when this method has been called.
+ */
+ public boolean close() {
+ // This is the only place in this class where we will null the socket
+ // object. Since this method can be called concurrently from different
+ // threads, lets do this under the lock.
+ LocalSocket socket;
+ synchronized (this) {
+ socket = mSocket;
+ mSocket = null;
+ }
+ if (socket != null) {
+ try {
+ // Force all I/O to stop before closing the socket.
+ socket.shutdownInput();
+ socket.shutdownOutput();
+ socket.close();
+ if (DEBUG) Log.d(TAG, "Socket is closed for " + mChannelName);
+ return true;
+ } catch (IOException e) {
+ Loge("Exception " + e + " while closing Socket for " + mChannelName);
+ }
+ }
+ return false;
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ Log.e(TAG, log);
+ }
+
+ private void Logw(String log) {
+ Log.w(TAG, log);
+ }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
index cd35833..705bd07 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
@@ -17,9 +17,7 @@
package com.android.tools.sdkcontroller.service;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import android.app.Activity;
import android.app.Notification;
@@ -33,33 +31,32 @@ import android.util.Log;
import com.android.tools.sdkcontroller.R;
import com.android.tools.sdkcontroller.activities.MainActivity;
-import com.android.tools.sdkcontroller.handlers.BaseHandler;
-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;
-import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;
-import com.android.tools.sdkcontroller.handlers.SensorsHandler;
-import com.android.tools.sdkcontroller.lib.EmulatorConnection;
-import com.android.tools.sdkcontroller.lib.EmulatorConnection.EmulatorConnectionType;
-import com.android.tools.sdkcontroller.lib.EmulatorListener;
+import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;
+import com.android.tools.sdkcontroller.handlers.SensorChannel;
+import com.android.tools.sdkcontroller.lib.Connection;
+import com.android.tools.sdkcontroller.lib.Channel;
/**
* The background service of the SdkController.
* There can be only one instance of this.
* <p/>
- * The service manages a number of action "handlers" which can be seen as individual tasks
- * that the user might want to accomplish, for example "sending sensor data to the emulator"
- * or "sending multi-touch data and displaying an emulator screen".
+ * The service manages a number of SDK controller channels which can be seen as
+ * individual tasks that the user might want to accomplish, for example "sending
+ * sensor data to the emulator" or "sending multi-touch data and displaying an
+ * emulator screen".
* <p/>
- * Each handler currently has its own emulator connection associated to it (cf class
- * {@code EmuCnxHandler} below. However our goal is to later move to a single connection channel
- * with all data multiplexed on top of it.
+ * Each channel connects to the emulator via UNIX-domain socket that is bound to
+ * "android.sdk.controller" port. Connection class provides a socket server that
+ * listens to emulator connections on this port, and binds new connection with a
+ * channel, based on channel name.
* <p/>
- * All the handlers are created when the service starts, and whether the emulator connection
- * is successful or not, and whether there's any UI to control it. It's up to the handlers
- * to deal with these specific details. <br/>
- * For example the {@link SensorsHandler} initializes its sensor list as soon as created
- * and then tries to send data as soon as there's an emulator connection.
- * On the other hand the {@link MultiTouchHandler} lays dormant till there's an UI interacting
- * with it.
+ * All the channels are created when the service starts, and whether the emulator
+ * connection is successful or not, and whether there's any UI to control it.
+ * It's up to the channels to deal with these specific details. <br/>
+ * For example the {@link SensorChannel} initializes its sensor list as soon as
+ * created and then tries to send data as soon as there's an emulator
+ * connection. On the other hand the {@link MultiTouchChannel} lays dormant till
+ * there's an UI interacting with it.
*/
public class ControllerService extends Service {
@@ -68,12 +65,17 @@ public class ControllerService extends Service {
* http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
*/
+ /** Tag for logging messages. */
public static String TAG = ControllerService.class.getSimpleName();
+ /** Controls debug log. */
private static boolean DEBUG = true;
-
/** Identifier for the notification. */
private static int NOTIF_ID = 'S' << 24 + 'd' << 16 + 'k' << 8 + 'C' << 0;
+ /** Connection to the emulator. */
+ public Connection mConnection;
+
+
private final IBinder mBinder = new ControllerBinder();
private List<ControllerListener> mListeners = new ArrayList<ControllerListener>();
@@ -86,8 +88,6 @@ public class ControllerService extends Service {
/** Internal error reported by the service. */
private String mServiceError = "";
- private final Set<EmuCnxHandler> mHandlers = new HashSet<ControllerService.EmuCnxHandler>();
-
/**
* Interface that the service uses to notify binded activities.
* <p/>
@@ -119,7 +119,7 @@ public class ControllerService extends Service {
public void addControllerListener(ControllerListener listener) {
assert listener != null;
if (listener != null) {
- synchronized(mListeners) {
+ synchronized (mListeners) {
if (!mListeners.contains(listener)) {
mListeners.add(listener);
}
@@ -134,7 +134,7 @@ public class ControllerService extends Service {
*/
public void removeControllerListener(ControllerListener listener) {
assert listener != null;
- synchronized(mListeners) {
+ synchronized (mListeners) {
mListeners.remove(listener);
}
}
@@ -149,34 +149,24 @@ public class ControllerService extends Service {
}
/**
- * Indicates when <em>all</all> the communication channels for all handlers
- * are properly connected.
+ * Indicates when <em>any</all> of the SDK controller channels is connected
+ * with the emulator.
*
- * @return True if all the handler's communication channels are connected.
+ * @return True if any of the SDK controller channels is connected with the
+ * emulator.
*/
public boolean isEmuConnected() {
- for (EmuCnxHandler handler : mHandlers) {
- if (!handler.isConnected()) {
- return false;
- }
- }
- return true;
+ return mConnection.isEmulatorConnected();
}
/**
- * Returns the handler for the given type.
+ * Returns the channel instance for the given type.
*
- * @param type One of the {@link HandlerType}s. Must not be null.
+ * @param name One of the channel names. Must not be null.
* @return Null if the type is not found, otherwise the handler's unique instance.
*/
- public BaseHandler getHandler(HandlerType type) {
- for (EmuCnxHandler handler : mHandlers) {
- BaseHandler h = handler.getHandler();
- if (h.getType() == type) {
- return h;
- }
- }
- return null;
+ public Channel getChannel(String name) {
+ return mConnection.getChannel(name);
}
}
@@ -220,101 +210,10 @@ public class ControllerService extends Service {
super.onDestroy();
}
- // ------
-
- /**
- * Wrapper that associates one {@link EmulatorConnection} with
- * one {@link BaseHandler}. Ideally we would not need this if all
- * the action handlers were using the same port, so this wrapper
- * is just temporary.
- */
- private class EmuCnxHandler implements EmulatorListener {
-
- private EmulatorConnection mCnx;
- private boolean mConnected;
- private final BaseHandler mHandler;
-
- public EmuCnxHandler(BaseHandler handler) {
- mHandler = handler;
- }
-
- @Override
- public void onEmulatorConnected() {
- mConnected = true;
- notifyStatusChanged();
- }
-
- @Override
- public void onEmulatorDisconnected() {
- mConnected = false;
- notifyStatusChanged();
- }
-
- @Override
- public String onEmulatorQuery(String query, String param) {
- if (DEBUG) Log.d(TAG, mHandler.getType().toString() + " Query " + query);
- return mHandler.onEmulatorQuery(query, param);
- }
-
- @Override
- public String onEmulatorBlobQuery(byte[] array) {
- if (DEBUG) Log.d(TAG, mHandler.getType().toString() + " BlobQuery " + array.length);
- return mHandler.onEmulatorBlobQuery(array);
- }
-
- EmuCnxHandler connect() {
- assert mCnx == null;
-
- mCnx = new EmulatorConnection(this);
-
- // Apps targeting Honeycomb SDK can't do network IO on their main UI
- // thread. So just start the connection from a thread.
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- // This will call onEmulatorBindResult with the result.
- mCnx.connect(mHandler.getPort(), EmulatorConnectionType.SYNC_CONNECTION);
- }
- }, "EmuCnxH.connect-" + mHandler.getType().toString());
- t.start();
-
- return this;
- }
-
- @Override
- public void onEmulatorBindResult(boolean success, Exception e) {
- if (success) {
- mHandler.onStart(mCnx, ControllerService.this /*context*/);
- } else {
- Log.e(TAG, "EmuCnx failed for " + mHandler.getType(), e);
- String msg = mHandler.getType().toString() + " failed: " +
- (e == null ? "n/a" : e.toString());
- addError(msg);
- }
- }
-
- void disconnect() {
- if (mCnx != null) {
- mHandler.onStop();
- mCnx.disconnect();
- mCnx = null;
- }
- }
-
- boolean isConnected() {
- return mConnected;
- }
-
- public BaseHandler getHandler() {
- return mHandler;
- }
- }
-
private void disconnectAll() {
- for(EmuCnxHandler handler : mHandlers) {
- handler.disconnect();
+ if (mConnection != null) {
+ mConnection.disconnect();
}
- mHandlers.clear();
}
/**
@@ -324,9 +223,15 @@ public class ControllerService extends Service {
try {
disconnectAll();
- assert mHandlers.isEmpty();
- mHandlers.add(new EmuCnxHandler(new MultiTouchHandler()).connect());
- mHandlers.add(new EmuCnxHandler(new SensorsHandler()).connect());
+ // Bind to SDK controller port, and start accepting emulator
+ // connections.
+ mConnection = new Connection(ControllerService.this);
+ mConnection.connect();
+
+ // Create and register sensors channel.
+ mConnection.registerChannel(new SensorChannel(ControllerService.this));
+ // Create and register multi-touch channel.
+ mConnection.registerChannel(new MultiTouchChannel(ControllerService.this));
} catch (Exception e) {
addError("Connection failed: " + e.toString());
}
@@ -340,15 +245,15 @@ public class ControllerService extends Service {
}
private void notifyErrorChanged() {
- synchronized(mListeners) {
+ synchronized (mListeners) {
for (ControllerListener listener : mListeners) {
listener.onErrorChanged();
}
}
}
- private void notifyStatusChanged() {
- synchronized(mListeners) {
+ public void notifyStatusChanged() {
+ synchronized (mListeners) {
for (ControllerListener listener : mListeners) {
listener.onStatusChanged();
}
@@ -368,7 +273,7 @@ public class ControllerService extends Service {
* An internal utility method to add a line to the error string and notify listeners.
* @param error A non-null non-empty error line. \n will be added automatically.
*/
- private void addError(String error) {
+ public void addError(String error) {
Log.e(TAG, error);
if (mServiceError.length() > 0) {
mServiceError += "\n";
@@ -397,10 +302,10 @@ public class ControllerService extends Service {
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(
- this, //context
- 0, //requestCode
- intent, //intent
- 0 // pending intent flags
+ this, //context
+ 0, //requestCode
+ intent, //intent
+ 0 //pending intent flags
);
n.setLatestEventInfo(this, text, text, pi);
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java b/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
index d612769..30bccdc 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
@@ -17,6 +17,7 @@
package com.android.tools.sdkcontroller.views;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import android.content.Context;
import android.graphics.Bitmap;
@@ -186,19 +187,19 @@ public class MultiTouchView extends View {
/**
* Constructs touch event message to be send to emulator.
*
- * @param sb String builder where to construct the message.
+ * @param bb ByteBuffer where to construct the message.
* @param event Event for which to construct the message.
* @param ptr_index Index of the motion pointer for which to construct the
* message.
*/
- public void constructEventMessage(StringBuilder sb, MotionEvent event, int ptr_index) {
- sb.append(" pid=").append(event.getPointerId(ptr_index));
+ public void constructEventMessage(ByteBuffer bb, MotionEvent event, int ptr_index) {
+ bb.putInt(event.getPointerId(ptr_index));
if (mRotateDisplay == false) {
- sb.append(" x=").append((int) (event.getX(ptr_index) / mDx));
- sb.append(" y=").append((int) (event.getY(ptr_index) / mDy));
+ bb.putInt((int) (event.getX(ptr_index) / mDx));
+ bb.putInt((int) (event.getY(ptr_index) / mDy));
} else {
- sb.append(" x=").append((int) (event.getY(ptr_index) / mDy));
- sb.append(" y=").append((int) (getWidth() - event.getX(ptr_index) / mDx));
+ bb.putInt((int) (event.getY(ptr_index) / mDy));
+ bb.putInt((int) (getWidth() - event.getX(ptr_index) / mDx));
}
// At the system level the input reader takes integers in the range
// 0 - 100 for the pressure.
@@ -207,7 +208,7 @@ public class MultiTouchView extends View {
if (pressure > 100) {
pressure = 100;
}
- sb.append(" pressure=").append(pressure);
+ bb.putInt(pressure);
}
/***************************************************************************