aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVladimir Chtchetkine <vchtchetkine@google.com>2012-04-19 09:47:30 -0700
committerVladimir Chtchetkine <vchtchetkine@google.com>2012-04-30 07:17:43 -0700
commita163f990623ef4697967c6ac34d2a7f9e324f8ff (patch)
treea31ba7e2701b4944c69e6d29bb0499e71631bac6
parent169b1b34c6d359994486bf99645bcc262a568dfb (diff)
downloadsdk-a163f990623ef4697967c6ac34d2a7f9e324f8ff.zip
sdk-a163f990623ef4697967c6ac34d2a7f9e324f8ff.tar.gz
sdk-a163f990623ef4697967c6ac34d2a7f9e324f8ff.tar.bz2
Implements new communication protocol with the emulator.
The major differences with the old protocol are: 1. All of the emulation handlers are bound to a sinlge UNIX-domain port android.sdk.controller. Note though that although there is one port to bind, each hander communicates with the emulator over its own socket connection. 2. As the result of 1) communication with the emulator is fairly simplified, and doesn't require complex binding, and state monitoring. 3. Messages and queries are no longer string based, but are packed to some binary formats. Change-Id: Icb7a535c3d10a887541ba7a8b3ec1439e0e238ce
-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);
}
/***************************************************************************